goldendb中并发 Update 大量锁超时场景
goldendb中并发 Update 大量锁超时场景
注意:本文档内容涉及内部技术细节,仅供内部使用,不得外传。
1. 多节点复制表 - 并发更新同一条记录
1.1 问题描述
在多节点复制表(Multi-Node Replicated Table)场景下,当多个客户端并发更新同一条记录时,可能引发分布式假死锁,导致锁超时报错。
场景流程:
- Client 1 发起
update row 1-> Proxy -> 下发至分片g1和g2。 - Client 2 发起
update row 1-> Proxy -> 下发至分片g1和g2。 - Proxy 在循环内下发指令(先
g1后g2),逻辑上看似串行,但实际网络传输中是并发的。 - 受网络延迟、CPU 负载等影响,数据库分片收到请求的顺序可能不一致。
1.2 原因分析
若出现以下时序,将形成死锁:
- Client 1 的更新请求:
g1先收到并加锁成功,g2后收到。 - Client 2 的更新请求:
g2先收到并加锁成功,g1后收到。
结果:
- Client 1 持有
g1锁,等待g2锁。 - Client 2 持有
g2锁,等待g1锁。 - 结局:形成分布式死锁,必须等待其中一个事务锁超时(
innodb_lock_wait_timeout)才能解开。
对比说明:在 CW(特定版本/模式)下不会出现此问题,因为 Proxy 做了优化:更新多节点复制表时,会先下发
select for update到第一个分片,锁定成功后再下发到其他分片。这会将单条update拆分为:start transaction->select for update (g1)->select for update (others)->update->commit。
1.3 解决方案
- 架构调整:对于需要高频并发更新相同记录的表,严禁使用多节点复制表。
- 替代方案:
- 改用 单节点复制表。
- 采用其他合适的分片规则。
- 适用场景限制:多节点复制表仅适用于参数库、配置库等几乎没有写操作的场景。
创建单节点复制表的语法示例:
CREATE TABLE dbmt.cdr_xxx_log (
`id` bigint NOT NULL,
`table_name` varchar(200) NOT NULL,
`partition_name` varchar(200) NOT NULL,
`xxx` bigint DEFAULT NULL,
`begin_time` datetime DEFAULT NULL,
`end_time` datetime DEFAULT NULL,
`FLAG` int NOT NULL DEFAULT '0',
`owner` varchar(200) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin
distributed by duplicate(g1);
查看表的完整创建语句:
SHOW CREATE TABLE schema_name.table_name storagedb g1\G
2. 多节点 Hash 表 - 范围更新涉及相同记录
2.1 问题描述
在多节点 Hash 分片表中,执行范围更新(Range Update)时,如果更新的数据集在不同分片间存在逻辑上的“相同记录”依赖,或更新语句导致每个分片都需要参与且存在资源竞争,其本质与上述多节点复制表的死锁原因相同。
2.2 原因分析
- 当
update涉及多条记录且跨越多个分片时,如果有相同的记录逻辑或资源竞争,相当于在模拟多节点复制表的更新行为。 - CW 优化无效:由于这不是严格定义的“多节点复制表”,Proxy 不会触发“先锁第一个分片”的优化逻辑,因此无法避免死锁。
2.3 解决方案
- 修改分片规则:针对有范围更新需求的业务,调整分片策略。
- 改为 单节点复制表。
- 改为 Range 分布(范围分片),确保范围更新尽量落在单个分片内或按序处理。
3. 特殊模型 + DDL 导致的分布式死锁
3.1 问题描述
在业务压测或特定场景下,长事务更新与 DDL 操作并发执行时,可能引发复杂的分布式死锁。
业务压测模型:
begin;
update t1 set money = money + 1 where id = 1; -- 操作分片 g1
update t1 set money = money - 1 where id = 100001; -- 操作分片 g2
commit;
同时后台执行 ALTER TABLE 等 DDL 操作。
3.2 原因分析
- 事务交叉持锁:
- 事务 A:先更新
g1,再更新g2。 - 事务 B:先更新
g2,再更新g1。
- 事务 A:先更新
- DDL 介入:
- 在两个事务分别持有部分分片锁的时刻,DDL 语句下发到所有分片。
- DDL 需要获取元数据锁(MDL X 锁),但被未提交的事务阻塞。
- 死锁形成:
- 事务 A 的
update g2等待 DDL 释放锁。 - 事务 B 的
update g1等待 DDL 释放锁。 - DDL 在两个分片上均等待事务 A 和 B 提交以获取 MDL-X 锁。
- 事务 A 的
- 最终表现:
- 系统陷入僵局,直到
innodb_lock_wait_timeout或lock_wait_timeout触发,强制释放锁。 - 若业务端设置的
socket_timeout小于数据库锁超时时间,业务会先报超时断链。
- 系统陷入僵局,直到
3.3 解决方案
配置 CN 组件使 DDL 串行执行,避免并发 DDL 加剧锁竞争。
在配置文件中进行如下设置:
# 指定 ddl 的执行顺序
# 默认为 0: 并行执行
# 修改为 1: 串行执行
# unit: NA, range: 0,1, default: 0, dynamic: no;
ddl_execute_serial = 1
总结与建议
| 场景 | 核心问题 | 推荐解决方案 |
|---|---|---|
| 多节点复制表并发写 | 网络无序导致分布式假死锁 | 禁用多节点复制表用于写场景;改用单节点复制或合适分片 |
| Hash 表范围更新 | 跨分片更新逻辑类似复制表,且无 Proxy 优化 | 修改分片规则为单节点复制或 Range 分布 |
| 事务更新 + DDL 并发 | 事务持锁与 DDL 争抢 MDL 锁形成死锁 | 设置 ddl_execute_serial = 1 开启 DDL 串行执行 |
- 感谢你赐予我前进的力量
赞赏者名单
因为你们的支持让我意识到写文章的价值🙏
本文是原创文章,采用 CC BY-NC-ND 4.0 协议,完整转载请注明来自 龙羽

