MySQL打开的文件描述符限制

2010-10-21  |  23:19分类:MySQL,代码细节  |  

如果遇到如下错误:

Can't open file: '.\test\mytable.frm' (errno: 24)
shell> perror 24
OS error code 24: Too many open files

这就是MySQL的文件描述不够用了。先说解决办法,再说背后的原因吧。

1. 如何解决

第一步:设置OS参数(如果你有权限的话):

文件/etc/security/limits.conf新增如下行:

mysql soft nofile 65535
mysql hard nofile 65535

上面的配置,是OS限制各个用户能够打开的文件描述符限制(hard soft区别参看man ulimit),新增上面两行,表示mysql用户能够打开65535个文件描述符(可以使用lsof -u mysql|wc -l查看当前打开了多少个文件描述符)。

第二步:修改MySQL参数:
在MySQL配置文件my.cnf中新增下面的行

open_files_limit = 65535
innodb_open_files=65535

然后重启你的MySQL一般问题就解决了。

2. 背后的问题


上面的办法一般就能解决问题了。不过在实践中发现,在my.cnf中设置的参数open_files_limit值是无效的,即MySQL启动后open_files_limit始终以OS的文件描述符为准。(版本MySQL5.1.45 RHEL5.4)

root@(none) 11:27:07>show global variables like "%open_files_limit%"; +------------------+-------+ | Variable_name | Value | +------------------+-------+ | open_files_limit | 20000 | +------------------+-------+ 1 row in set (0.01 sec) root@(none) 11:27:09>system ulimit -n 20000

那my.cnf参数open_files_limit是否真的是没用呢?接下来会是一篇很长、很蛋疼的关于该问题的研究,如果不是很有时间,不建议看下去。

3. 源代码中如何设置open_files_limit
3.1 实验验证

配置文件中配置:open_files_limit = 10000;$ulimit -n 20000;启动数据库,观察:

root@(none) 11:48:57>show global variables like "%open_files_limit%"; +------------------+-------+ | Variable_name | Value | +------------------+-------+ | open_files_limit | 20000 | +------------------+-------+

看到参数open_files_limit确实没有作用(已经实验了很多次了)。

3.1 代码追踪watch

首先使用gdb/watch观察open_files_limit变量值,

Old value = 0 New value = 10000 0x000000000086a6f2 in setval () Old value = 10000 New value = 20000 0x00000000005ba159 in init_common_variables ()

看到,配置文件my.cnf中的10000,又没有生效。

3.2 梦境:更多的代码细节
wanted_files= 10+max_connections+table_cache_size*2; max_open_files= max(max(wanted_files, max_connections*5), open_files_limit); files= my_set_max_open_files(max_open_files);

这一段可以看到,首先会比较open_files_limit,max_connections*5和10+max_connections+table_cache_size*2中最大值,并调用函数my_set_max_open_files设置文件描述符限制。

继续跟踪函数my_set_max_open_files,在文件mysys/my_file.c中:

uint my_set_max_open_files(uint files) { struct st_my_file_info *tmp; DBUG_ENTER("my_set_max_open_files"); DBUG_PRINT("enter",("files: %u my_file_limit: %u", files, my_file_limit)); files= set_max_open_files(min(files, OS_FILE_LIMIT)); ## #define OS_FILE_LIMIT 65535 ##

这里继续,调用了set_max_open_files,仍然在文件mysys/my_file.c:

static uint set_max_open_files(uint max_file_limit) { struct rlimit rlimit; uint old_cur; DBUG_ENTER("set_max_open_files"); DBUG_PRINT("enter",("files: %u", max_file_limit)); if (!getrlimit(RLIMIT_NOFILE,&rlimit)) { old_cur= (uint) rlimit.rlim_cur; DBUG_PRINT("info", ("rlim_cur: %u rlim_max: %u", (uint) rlimit.rlim_cur, (uint) rlimit.rlim_max)); if (rlimit.rlim_cur == RLIM_INFINITY) rlimit.rlim_cur = max_file_limit; if (rlimit.rlim_cur >= max_file_limit) DBUG_RETURN(rlimit.rlim_cur); /* purecov: inspected */ rlimit.rlim_cur= rlimit.rlim_max= max_file_limit; if (setrlimit(RLIMIT_NOFILE, &rlimit)) max_file_limit= old_cur; /* Use original value */ else { rlimit.rlim_cur= 0; /* Safety if next call fails */ (void) getrlimit(RLIMIT_NOFILE,&rlimit); DBUG_PRINT("info", ("rlim_cur: %u", (uint) rlimit.rlim_cur)); if (rlimit.rlim_cur) /* If call didn't fail */ max_file_limit= (uint) rlimit.rlim_cur; } } DBUG_PRINT("exit",("max_file_limit: %u", max_file_limit)); DBUG_RETURN(max_file_limit); }

上面的代码中:

if (rlimit.rlim_cur >= max_file_limit) DBUG_RETURN(rlimit.rlim_cur); /* purecov: inspected */

看到,如果当前的OS文件描述符(rlimit.rlim_cur)限制大于配置文件的参数max_file_limit,则函数返回不再对使用描述符做任何限制。

所以,当配置文件设置的open_files_limit值小于OS文件描述符限制时,将不会调用setrlimit,即文件描述符限制是OS的限制值。

这里代码还可以看到,如果open_files_limit的值大于OS文件描述符限制时,将会尝试调用setrlimit(RLIMIT_NOFILE, &rlimit)将描述符设置open_files_limit,事实上,setrlimit会调用失败。

所以,当配置文件设置的open_files_limit值大于OS文件描述符限制时,会调用setrlimit,但是会失败,即文件描述符限制仍然是OS的限制值。

即,无论如何,MySQL打开的文件描述符限制都是OS的文件描述符限制,和配置文件中open_files_limit的设置没有关系。

(如果看到这,说明你已经成功进入第二层梦境了:my_set_max_open_files和set_max_open_files)

4 代码外的代码

估计看到这,看客已经晕了吧。因为这个问题并没有那么重要,解决办法也很简单,所以确实也不用追究的这么细。不过都到这儿了,我打算继续。

在如果想设置open-files-limit也有成功的时候,那就是使用root帐号,运行mysqld_safe脚本启动MySQL(或者使用mysql.server启动),如果使用--open-files-limit是可以成功设置的:

./mysqld_safe --open-files-limit=25000 & root@(none) 02:50:54>show variables like "%open_files_limit%"; +------------------+-------+ | Variable_name | Value | +------------------+-------+ | open_files_limit | 25000 | +------------------+-------+

这是因为在mysqld_safe中做了如下处理:

if test -w / -o "$USER" = "root" then ...... if test -n "$open_files" then ulimit -n $open_files append_arg_to_args "--open-files-limit=$open_files" fi fi

可以看到,直接或间接使用mysqld_safe启动MySQL时,其实是在启动mysqld程序之前,调用了ulimit -n $open_files来实现文件描述符的限制。

参考:

1. MySQL Source Code

2. mysql如何计算打开文件数

3. MySQL Manual

广告时间:工作机会--MySQL Hacker

喜欢本文,那就收藏到:

7条评论 关于 “MySQL打开的文件描述符限制”

  1. pandy 发表于: 十月 26th, 2010 17:29

    Orczhou你好,我所在的网站有一个mysql技术问题想请你解决,不知道您方便留下email地址吗?

  2. orczhou 发表于: 十月 26th, 2010 20:52

    邮件orczhou at gmail dot com

  3. 7up 发表于: 十月 27th, 2010 23:06

    我在想,mysql的这个比较过程是什么逻辑阿。
    既然提供了一个open_files_limit参数,那就是想提供一个功能:在os的限定条件内,让我们能在mysql层面限定最大打开文件数的数目。

    所以感觉比较的逻辑应该是:
    rlimit.rlim_cur > max_file_limit ? DBUG_RETURN(max_file_limit) : DBUG_RETURN(rlimit.rlim_cur);

    而非:
    if (rlimit.rlim_cur > max_file_limit)
    DBUG_RETURN(max_file_limit); /* purecov: inspected */

  4. xupengfey 发表于: 二月 26th, 2011 13:23

    严重同意。

  5. open/close table on mysql | 人生不过如此 发表于: 三月 3rd, 2011 10:26

    [...] 在配置文件中,我们也可以看到open_files_limit参数,但是如果你设置该参数,重启主机后,该参数的值还是以系统的文件描述符为准。 This entry was posted in database. Bookmark the permalink. ← Incorrect datetime value [...]

  6. huanghualiang 发表于: 五月 30th, 2014 11:06

    您好!我的版本是MySQL-5.5.36,我测试的结果是open-files-limit总是遵循:
    wanted_files= 10+max_connections+table_cache_size*2;
    max_open_files= max(max(wanted_files, max_connections*5),
    open_files_limit);
    files= my_set_max_open_files(max_open_files);
    以root启动mysqld_safe --open-files-limit=25000 还是遵循上面的规则,不会因为root而改变,求解。。。

  7. 无为 发表于: 七月 17th, 2015 09:39

    O(∩_∩)O谢谢分享,我转载了。
    http://blog.csdn.net/rookie_ceo/article/details/46922883


发表您的评论