news 2026/9/26 4:55:30

线上惨案复盘:一个 ADD PARTITION 是怎么把正常 INSERT 逼出死锁的?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
线上惨案复盘:一个 ADD PARTITION 是怎么把正常 INSERT 逼出死锁的?

线上搞分区表,DBA和运维最爱干的事之一,就是按月无缝加分区:

ALTER TABLE orders ADD PARTITION ( PARTITION p202603 VALUES LESS THAN (20260401) );

看官方文档描述让人非常安心:RANGE/LIST的ADD PARTITION是Inplace操作,几乎不搬数据,业务还能继续写入。结果半夜脚本一跑,监控突然冒出一堆Deadlock found when trying to get lock,受害者偏偏是白天跑得好好的普通INSERT语句。

很多人排查第一反应是:“是不是新分区被锁了?”

别猜了,并不是。MySQL根本没有“每个分区单独一把大锁”这种设计。ADD PARTITION和INSERT真正死磕的,是整张表的元数据锁(MDL)。死锁往往就发生在这样一个微妙瞬间:DDL收尾必须独占整张表,但某个业务事务死活还没松手。

下面用一张表、两个会话,分析底层的逻辑。


1. 一条常规的ALTER是怎么逼死正常INSERT的?

先建一张典型的按月分区订单表:

CREATE TABLE orders ( id BIGINT NOT NULL AUTO_INCREMENT, order_day INT NOT NULL, -- 例如20260315 amount DECIMAL(12,2), PRIMARY KEY (id, order_day) ) PARTITION BY RANGE (order_day) ( PARTITION p202601 VALUES LESS THAN (20260201), PARTITION p202602 VALUES LESS THAN (20260301) );

会话A(运维DDL):

ALTER TABLE orders ADD PARTITION ( PARTITION p202603 VALUES LESS THAN (20260401) );

会话B(业务DML,模拟包含耗时逻辑的长事务):

BEGIN; INSERT INTO orders (order_day, amount) VALUES (20260215, 99.00); -- 故意不COMMIT,模拟长事务或多语句事务中间态

白天业务高峰期,会话B这种“开了事务、写了几条、还在等下游RPC响应”的场景到处都是。 一旦会话A的ADD PARTITION撞上这种事务就会直接卡住,甚至跟其他正在写的会话绕成死锁。


2. 所谓的Online其实是“中间松,两头紧”

对于RANGE分区的ADD PARTITION,InnoDB大致分三步走:

  1. 准备阶段(Prepare):短暂要个独占(X锁)。

  2. 干活阶段(Inplace):把锁降下来,允许业务继续INSERT/UPDATE。(这就是所谓的Online阶段)。

  3. 收尾阶段(Commit前):再次强制升成独占(X锁),改数据字典、换分区定义。

坑就埋在第3步。

中间放行是为了少挡业务,但收尾必须独占。因为底层数据字典和分区边界变了,总不能一边有人按旧地图写,一边你在换新地图吧。

源码里InnoDB对“要不要搬数据”分得很清(位于handler0alter.cc的alter_parts::need_copy):

  • RANGE/LIST的ADD PARTITION一般不用拷数据。

  • 所以ha_innopart::check_if_supported_inplace_alter直接返回HA_ALTER_INPLACE_NO_LOCK_AFTER_PREPARE。

说白了就是:“准备阶段排他,干活时放开写,提交前再排他一次。” SQL层真正收口的地方在mysql_inplace_alter_table()(sql_table.cc):

1. Prepare完成 → 锁降级到SU(允许并发写) 2. Inplace主阶段干活 3. Commit前 → 调wait_while_table_is_used() → 锁强制升级到MDL_EXCLUSIVE(X锁)

wait_while_table_is_used()干的活极其霸道:把当前表上的共享锁升成X锁,并把别人手里打开的表实例全清掉。

结论很清楚:ADD PARTITION不是全程无锁,是“中间松,两头紧”。


3. 别光盯行锁了,MDL才是幕后黑手!

3.1业务INSERT在拿什么锁?

INSERT打开表时拿的是MDL_SHARED_WRITE(SW锁)。 潜台词很明确:“我要改这张表的数据,谁也别动表结构。”

只要事务不提交,这把SW锁就死死挂在当前会话上。行锁、自增锁那是InnoDB引擎层的事;对ADD PARTITION来说,真正挡提交的首要大爹,就是这把表级的MDL锁。

3.2ADD PARTITION在拿什么锁?

ALTER刚开始一般拿的是MDL_SHARED_UPGRADABLE(SU锁):共享,但声明“我后面要升级”。

查下mdl.cc里的兼容矩阵:

  • SU和SW:完全兼容。所以主阶段业务能愉快地INSERT。

  • X和任何锁:互斥。提交前要升X锁,就必须死等所有的SW锁走完。

于是时间线就变成了这样:

到这步系统还只是阻塞(Lock Wait)。要弄成死锁,还得再加一把火。


4. 未提交的长事务是怎么把等待链绕成死锁的?

最容易在生产环境复现的死锁,压根不是“两个INSERT抢同一行”,而是:DDL在等DML释放MDL锁;而DML(或另一条语句)又在等DDL已经占住的资源。

来个最贴近线上的版本(跨表长事务):

光盯着orders这一张表查,现场往往是这样的:

  • 会话A(ADD PARTITION):手里捏着SU,眼巴巴等X锁(被B的SW锁挡着)。

  • 会话B(业务事务):手里攥着SW锁+几把行锁,同时又在等某个被A间接卡住的资源。

  • 结果:环状等待,死锁成型,MySQL直接挑个软柿子回滚。

很多人排查时看SHOW ENGINE INNODB STATUS只看到了行锁那一半。另一半其实藏在MDL里——去performance_schema.metadata_locks查一下就能对上:谁死死捏着SHARED_WRITE,谁又在苦等EXCLUSIVE。


5. 4个锚点钉死InnoDB锁的降级与强制升级

不用通读几万行的ALTER代码,这四个锚点就够了:

①引擎层声明:不搬数据→Prepare后可放行写入在ha_innopart::check_if_supported_inplace_alter()里,因为alter_parts::need_copy()==false,直接抛出HA_ALTER_INPLACE_NO_LOCK_AFTER_PREPARE。

②SQL层降锁:Prepare用X锁,随后降级SU在mysql_inplace_alter_table()里针对*_AFTER_PREPARE:先upgrade_shared_lock(... MDL_EXCLUSIVE),准备成功后,再downgrade_lock(MDL_SHARED_UPGRADABLE)。这一降就是业务端觉得DDL不阻塞的根本原因。

③提交前升锁:再次强制升X同一函数后半段直接调wait_while_table_is_used(thd, table, HA_EXTRA_PREPARE_FOR_RENAME)。这玩意底层还是upgrade_shared_lock(... MDL_EXCLUSIVE)。所有没提交的SW锁全成了拦路虎。

④锁兼容矩阵:X锁六亲不认mdl.cc写得明明白白:想申请X锁,必须等所有冲突锁释放——包括SW。所以线上现象极其统一:

  • DDL跑到99%时业务还在丝滑写入。

  • 最后1%收尾时像踩了急刹车,INSERT瞬间堆积,紧接着死锁报警满天飞。

这不是分区算法有Bug,而是一致性锁模型本来就是这么设计的。


6. 同为分区变更,为什么ADD PARTITION最容易踩雷?

都在InnoDB分区ALTER框架下,不同操作痛感完全不同:

操作类型

是否要拷数据

主阶段锁倾向

对业务写入的体感

RANGE/LISTADD

否

几乎不挡写(SU)

中间松,收尾紧

RANGE/LISTDROP

否

几乎不挡写(SU)

同上

REORGANIZE

等

常要

主阶段就挡写(SNW)

早期就冲突,痛感反而直接

DROP空分区和ADD新边界,体感上都是“秒级元数据变更”,所以最容易被人在业务高峰期随手敲进终端——也最容易在收尾升X锁时被长事务精准狙击。反倒REORGANIZE这种大动作主阶段就限制写入,降低了最后关头诱发复杂死锁的概率。


7. 告别DDL死锁的5条建议

不想半夜被报警电话叫醒,搞分区变更时死守这几条红线:

  1. 加分区放低峰期,敲回车前先扫长事务动手前查下information_schema.innodb_trx和performance_schema.metadata_locks,要是目标表上挂着好几分钟没提交的事务,千万别头铁。

  2. 业务侧管好事务边界“插一条、调个慢接口、再更新一条”这种长事务是DDL天敌。维护期内事务越短越好。

  3. 别迷信Online DDL官方说的Online是指中间阶段。涉及数据字典的最终提交,永远需要一个绝对干净的独占窗口。

  4. 排查死锁别光盯行锁涉及DDL的死锁一定要看MDL监控,抓出谁拿着SW锁,谁在等X锁。

  5. 能用脚本提前建就别临时搞最佳实践就是写个定时任务,提前建好未来几个月的分区,彻底告别高峰期临时救火式ALTER。


以后碰到ADD PARTITION引发死锁,别再怀疑是不是这命令锁表了。它的机制就是故意在中间放行,结束前强行清场。当业务长事务死攥着SHARED_WRITE不放,而DDL又非要EXCLUSIVE锁不可时,稍微跟别的行锁缠一下,死锁闭环就成了。

不是分区表天生爱死锁,而是长事务刚好卡在了“高并发写入”和“元数据一致性”交接的那道缝隙里。下次加分区前,先问一句:“这表上还有没提交的事务吗?”,大半的坑就避开了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/26 4:54:48

算法市场模型性能优化六大技巧:从延迟画像到自动回滚

我们部门在搭建企业算法市场那段时间,最头疼的还真不是模型效果不达标,而是"模型明明本地跑得好好的,一上市场就卡成狗"。业务方陆续来投诉:有的说接口超时,有的说结果出来了但等了半天,还有的说…

作者头像 李华
网站建设 2026/9/26 4:54:43

PE文件自动查壳与脱壳完整指南:原理、实操与避坑

简介:自动查壳脱壳工具(exeinfope)是一款面向开发人员与逆向分析者的PE分析实用工具,可快速查看编译器信息、入口点、输入表/输出表等结构,判断是否加壳并给出脱壳引导;还能提取图片、EXE、压缩包、MSI、SW…

作者头像 李华
网站建设 2026/9/26 4:54:26

PCM数字音频原理与Python实操:从采样量化到WAV字节解析

1. 从CD到语音通话:为什么PCM是数字音频的地基先抛一个反直觉的事实:你手机里那些几十MB的WAV录音、微信语音消息、CD唱片上的音轨,虽然听感一个比一个"精细",但底层用的都是同一种技术——脉冲编码调制,也就…

作者头像 李华
网站建设 2026/9/26 4:54:06

压缩包隐写与取证分析:从ZIP结构到CTF实战

1. 压缩包不只是“打包”:从文件结构看隐写空间很多人对压缩包的理解停留在“把一堆文件压成一个小包,方便传输”这个层面。但如果你接触过CTF里的Misc方向,或者做过电子数据取证相关的工作,就会知道压缩包本身就是一个天然的隐写…

作者头像 李华
网站建设 2026/9/26 4:54:03

用Spring Boot搭建校园网络运维工单与设备监控系统

我们学校原来那个网络报修的流程,说出来同行都得摇头——学生宿舍断网了,先打电话给信息中心,信息中心登记完再转给对应的运维师傅,师傅修完了再回来填个Excel表。遇到设备离线、交换机端口异常这些问题,基本靠巡检时肉…

作者头像 李华
网站建设 2026/9/26 4:52:33

极化SAR特征提取实战:从散射矩阵到可训练数值特征

简介:本资源是一套面向遥感图像处理初学者与SAR方向研究生的极化SAR特征提取实践代码包,聚焦全极化SAR数据的H/A/α三参数分解这一核心预处理环节,解决地物分类、变化检测等任务中特征表达不足的痛点。压缩包共17个文件(29KB&…

作者头像 李华