分区表执行_SPLIT会锁全表,因InnoDB对分区操作常升级为全表MDL写锁,TiDB和PostgreSQL也存在调度阻塞或长事务锁问题;应改用灰度迁移(如双写+原子切换)而非原生DDL。
分区表执行 _SPLIT 时为什么会锁全表
mysql(尤其是 5.7/8.0 的 innodb)在对 range/list 分区表执行 alter table ... reorganize partition 或某些带 _split 语义的操作(如 tidb 的 split region、或自定义分表逻辑中模拟的“拆分”)时,底层往往需要重建目标分区的数据结构。innodb 默认会对涉及的分区加 x 锁,而很多版本和引擎实现中,这个操作会升级为对整个表加 mdl(metadata lock) 写锁——导致所有 dml 阻塞。
- 不是“只锁要拆的分区”,而是锁整个表元数据,哪怕你只动
p202406 - TiDB 虽无传统表锁,但
SPLIT REGION若在热点 Region 上执行,仍会触发调度阻塞和写放大,间接造成写延迟飙升 - PostgreSQL 的分区表(v10+)用
ATTACH/DETACH拆分,本身不锁全表,但若依赖INSERT ... SELECT迁移旧数据,就可能因长事务锁住源分区
如何避免 _SPLIT 过程中业务写入中断
核心思路是把“拆分”从原子 DDL 操作,变成可灰度、可回滚、不依赖全局锁的数据迁移流程。
- 提前建好新分区(或新物理表),用
CREATE TABLE ... PARTITION BY ...或CREATE TABLE LIKE复制结构,不带数据 - 通过应用层双写或
TRIGGER + binlog 解析同步增量:老分区继续接收写入,同时将新数据实时写入新分区 - 等存量数据迁移完成(可用
pt-archiver或自定义批处理),再用RENAME TABLE原子切换(仅毫秒级 MDL),而不是直接REORGANIZE - MySQL 8.0.29+ 支持
ALTER TABLE ... EXCHANGE PARTITION无锁交换,但要求表结构完全一致、且目标分区为空——适合预分配场景
_SPLIT 期间高并发读写冲突的典型表现
锁表只是表象,真正让服务雪崩的是资源争抢叠加等待链:
- 大量
Waiting for table metadata lock状态堆积,SHOW PROCESSLIST里能看到上百个alter和insert卡住 - 连接数暴涨,触发
max_connections限制,新请求连不上 - TiDB 中出现
Region is unavailable或write stall日志,Prometheus 上tikv_scheduler_pending_tasks_total持续上升 - PostgreSQL 中
pg_locks显示大量AccessExclusiveLock在父表上,即使你只操作子分区
不同数据库对 _SPLIT 的兼容性与替代方案
没有银弹,得按实际栈选路径:
- MySQL:放弃原生
REORGANIZE,改用pt-online-schema-change --chunk-index拆分,它通过触发器捕获变更,但要注意触发器性能损耗 - TiDB:优先用
SPLIT REGION TABLE t BETWEEN (0) AND (1000000) REGIONS 4,配合tidb_enable_table_partition = 'on',并确保split-region-max-rate不设过高(默认 2) - PostgreSQL:用
CREATE TABLE t_202407 PARTITION OF t FOR VALUES FROM ('2024-07-01') TO ('2024-08-01')动态加分区,再用INSERT INTO t_202407 SELECT ... FROM t WHERE ...迁移,全程不锁主表(只锁对应子表) - 如果用 MyCat/ShardingSphere 这类中间件,
_SPLIT实际是逻辑操作,真正风险在后端分片 DB 的 DDL 执行时机——必须配置allow-runtime-add-table并关闭自动同步 DDL
真正麻烦的从来不是“怎么拆”,而是“怎么让拆的时候没人感知”。DDL 可以重试,用户请求超时就直接失败。所以所有方案都要预留 fallback:比如双写开关、影子表回切 SQL、binlog 回滚位点记录——这些比选对函数名重要得多。










