先查MySQL进程实际打开的文件数:lsof -p $(pgrep mysqld) | wc -l,再对比cat /proc/$(pgrep mysqld)/limits | grep "Max open files",若远低于上限则需排查临时表、SQL未用索引或游标未释放等问题。

查清 MySQL 实际打开的文件数,别急着改配置
报 Too many open files 时,第一反应不是改 ulimit 或 open_files_limit,而是确认 MySQL 进程**真正在用多少个文件描述符**。系统限制可能远没到,但 MySQL 自己在疯狂开临时文件、没释放游标、或表缓存太小导致频繁开关表文件。
执行这两条命令快速定位:
-
lsof -p $(pgrep mysqld) | wc -l—— 统计当前 MySQL 进程打开的 fd 总数 -
cat /proc/$(pgrep mysqld)/limits | grep "Max open files"—— 查内核对该进程实际生效的上限(注意:它不一定等于ulimit -n,因为启动时继承的是父进程 limits)
如果前者接近后者,说明系统层确实卡死;如果前者只有几百、后者却是 65535,那问题大概率出在 SQL 层:比如 ORDER BY 或 GROUP BY 生成大量磁盘临时表、JOIN 字段类型不匹配导致索引失效、或者未关闭存储过程中的游标。
systemd 环境下 LimitNOFILE 必须显式设置
现代 Linux(CentOS/RHEL 7+、Ubuntu 16.04+)大多用 systemd 启动 MySQL,而 /etc/security/limits.conf 在这种环境下**默认不生效**——因为 systemd 不读该文件,除非你手动启用了 pam_limits.so,且服务未覆盖 limits。
真正起效的方式是直接修改 service 文件:
- 找到 MySQL 的 service 定义:
systemctl cat mysqld或find /usr/lib/systemd/system /etc/systemd/system -name "*mysql*.service" - 在
[Service]段末尾添加:LimitNOFILE=65535 - 执行:
systemctl daemon-reload && systemctl restart mysqld
验证是否生效:cat /proc/$(pgrep mysqld)/limits | grep "Max open files",输出值应与你设的 LimitNOFILE 一致。若仍是 1024,说明 service 文件没改对位置,或被其他同名 unit 覆盖了。
open_files_limit 参数为什么始终不变
MySQL 的 open_files_limit 不是你在 my.cnf 里写多少就生效多少。它是在启动时取以下三者中的最大值:
10 + max_connections + (table_open_cache * 2)max_connections * 5- 你配置的
open_files_limit值(或默认 5000)
但这个计算结果还受制于系统实际允许值:最终不会超过 /proc/$pid/limits 中的 Max open files。所以即使你在 [mysqld] 里写了 open_files_limit = 102400,只要 systemd 没给够 LimitNOFILE,MySQL 就会默默降级到系统允许的最大值,并在 error log 里写一句 Changed limits: max_open_files: 1024 max_connections: 151 table_cache: 400(注意这里 max_open_files 是降级后的值)。
检查方式:mysql -e "show variables like 'open_files_limit';",再比对 cat /proc/$(pgrep mysqld)/limits 输出的 soft/hard 值。
临时表和慢查询才是隐藏杀手
很多 case 改完 ulimit 和 LimitNOFILE 后仍报错,是因为根本原因不在系统限制,而在 SQL 行为本身。典型表现是:lsof 统计数波动剧烈、error log 频繁出现 Could not create temporary file、监控显示 Innodb_open_files 稳定但 Created_tmp_disk_tables 暴涨。
这时候要盯住这些点:
- 查慢日志:
long_query_time = 1开启后抓出未走索引的 JOIN 或 ORDER BY - 看执行计划:
EXPLAIN输出中type是ALL、key是NULL、Extra含Using filesort或Using temporary - 特别注意字段类型隐式转换:比如
VARCHAR列和INT常量比较,会导致索引失效,触发全表扫描 + 大量临时表 - 调大
tmp_table_size和max_heap_table_size(两者需一致),避免内存临时表自动转磁盘
这类问题不解决,哪怕把 LimitNOFILE 设到 100 万,只要并发来几个坏 SQL,照样爆 EMFILE。











