如果遇到如下错误:
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来实现文件描述符的限制。
参考: