MySQL 5.7 分区表不得超过128个,否则因每个分区独占.ibd文件句柄而耗尽open_files_limit,引发启动失败或ERROR 23;须用ALTER TABLE ... REMOVE PARTITIONING在线退化,并按场景调整分区粒度与数量。

MySQL 5.7 分区表超 128 个会直接触发 open_files_limit 耗尽,导致启动失败或 ERROR 23 (HY000): Out of resources when opening file —— 这不是性能问题,是硬性限制,必须砍到 128 以内。
为什么分区数超限会导致内存/文件句柄耗尽
MySQL 5.7 对每个分区都单独打开一个 .ibd 文件句柄,不共享。分区越多,占用的文件描述符(file descriptor)就越多。一旦突破系统 open_files_limit(常默认 1024 或 4096),mysqld 就无法继续打开新分区文件,表现为启动卡死、报错 ERROR 23,或运行中突然无法访问某分区。
更隐蔽的是:即使没报错,table_open_cache 值偏高时,每个分区还会额外缓存一份表结构元数据,进一步放大内存压力。
- 检查当前分区数:
SELECT COUNT(*) FROM information_schema.PARTITIONS WHERE TABLE_SCHEMA = 'your_db' AND TABLE_NAME = 'your_table'; - 确认系统限制:
cat /proc/$(pgrep mysqld)/limits | grep "Max open files" - RANGE 分区单区超 1500 万行 → B+ 树深度从 3 层涨到 4 层,每次索引查找多一次磁盘 IO,查询反而变慢
如何安全退化超限分区表(不锁全表、不丢数据)
别删数据,也别 DROP PARTITION,那是自找麻烦。正确做法是先退化为普通表,再按需重建:
- 执行
ALTER TABLE your_table REMOVE PARTITIONING;—— 这个操作在 5.7 中是**在线 DDL(ALGORITHM=INPLACE)**,只重写表定义,不重建数据,耗时短、锁表时间极短(通常毫秒级) - 退化后立刻检查:
SELECT COUNT(*) FROM information_schema.PARTITIONS WHERE TABLE_NAME = 'your_table';应返回 1 - 如果原表有大量历史数据且仍需分区,按新规划重建:比如把按天分区改为按月,用
REORGANIZE PARTITION合并相邻小分区,而不是 DROP + ADD(后者会锁全表 + 全量重建索引)
RANGE/HASH/LIST 分区各自的数量与大小红线
不同分区类型约束不同,不能套用同一套数字:
-
RANGE 分区:单分区行数 ≤ 1500 万(OLTP 场景建议压到 200–500 万);按时间分区(如
TO_DAYS(created_at))最容易踩坑,日均 20 万行的表,3 个月就逼近临界点,该切周/月粒度 -
HASH 分区:分区数必须是 2 的幂次(16/32/64),且 ≤ 64;设
PARTITIONS 30会导致严重数据倾斜(某些分区占 40%+ 数据),同时哈希冲突概率陡增,INSERT 反而变慢 - LIST 分区:单个分区枚举值 ≤ 32 个;超过后优化器可能放弃分区裁剪,隐式扫描全部分区
所有分区表上线前必须跑 EXPLAIN PARTITIONS SELECT ...,确认 partitions 列只显示目标分区名,否则就是白建。
SSD/NVMe 环境下也不能堆满 128 个分区
物理介质快 ≠ 分区可以无脑加。NVMe 上建议上限压到 96,SSD 建议 64–80,留出余量给系统文件、其他表、以及 MySQL 自身的 table_open_cache 和 performance_schema 内存开销。
最容易被忽略的一点:分区数变更后,innodb_buffer_pool_size 不需要同步调大——分区只是逻辑切分,底层仍是多个独立 .ibd 文件,buffer pool 按页加载,和分区数无直接关系。但若你之前为“扛住 200 个分区”而盲目调高了 buffer pool,现在就得按真实活跃数据集重新评估。


















