功能概述
TDSQL Boundless 提供抢占式 DDL 功能,用于解决分布式场景下, DDL 操作因其他节点上的长事务持有 MDL-S 锁而长时间等待甚至超时失败的问题。
开启抢占式 DDL 后,当 DDL 操作在目标节点等待 MDL-X 锁超过指定时间时,系统将主动终止阻塞 MDL-X 锁获取的会话,确保 DDL 操作能够顺利完成。
背景信息
TDSQL Boundless 分布式架构下的 DDL 锁协调
TDSQL Boundless 采用分布式架构,每个 SQLEngine 节点均具备独立的读写能力。当某个节点执行 DDL 操作时,需要在以下两个层面获取锁:
1. 本节点 MDL 锁: DDL 需要在发起节点获取目标表的 MDL-X 锁,与本节点上的活跃事务互斥。
2. 全局对象锁: DDL 需要通过元数据服务(TDMC)获取全局对象锁,确保同一时间只有一个节点能修改表结构,防止跨节点 DDL 冲突。
问题现象
在同一节点上,如果存在对目标表的长事务或大查询,这些会话会持有该表的 MDL-S 锁。DDL 操作需要获取 MDL-X 锁,两者互斥,导致 DDL 被阻塞。如果长事务在锁等待超时时间内未结束,DDL 将执行失败。
典型报错:
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
在未开启抢占式 DDL 的情况下,处理此类问题通常需要 DBA 手动定位并终止持锁会话,操作流程繁琐且响应不及时。
抢占式 DDL 的解决思路
抢占式 DDL 提供了一种自动化的解决方案:当 DDL 等待 MDL 锁超过指定时间后,系统将自动终止阻塞 DDL 的会话,释放 MDL 锁资源,使 DDL 操作能够继续执行。相比手动干预,这种方式响应更快、操作更可靠。
注意事项
业务影响: 开启抢占式 DDL 后,阻塞 DDL 的会话将被系统强制终止,会话上未提交的事务将被回滚。请评估业务对事务中断的容忍度。
建议在低峰期使用: 尽管抢占式 DDL 能自动处理锁冲突,仍建议在业务低峰期执行 DDL 变更,以减少对正常业务的影响。
参数说明
通过以下参数控制抢占式 DDL 的行为,可在控制台的参数配置页面或通过 SQL 命令进行设置。
参数 | 级别 | 说明 | 取值范围 | 默认值 |
tdsql_ddl_block_mode | Session | 控制普通线程(normal)DDL 锁获取行为,设置为 preemptive 时启用抢占式 DDL 逻辑 | preemptive nonblock default | preemptive |
tdsql_ddl_recovery_block_mode | Global | 控制恢复线程(recovery)DDL 锁获取行为,设置为 preemptive 时启用抢占式 DDL 逻辑;仅 GLOBAL 级别设置生效,SESSION 级别设置无效 | preemptive nonblock default | preemptive |
tdsql_ddl_preempt_after_wait_seconds | Session | 抢占式 DDL 等待 MDL‑X 锁的容忍时间,超过该时间后自动触发锁抢占流程。单位:秒 | 1 ~ 31536000 | 50 |
使用方法
1. 开启抢占式 DDL。
在执行 DDL 的会话中开启功能:
SET tdsql_ddl_block_mode = 'preemptive';-- tdsql_ddl_block_mode参数的默认值即为 'preemptive'
2. (可选)调整抢占等待时间。
例如设置为5秒:
SET tdsql_ddl_preempt_after_wait_seconds = 5;
3. (可选)配置恢复线程抢占式 DDL。
SET GLOBAL tdsql_ddl_recovery_block_mode = 'preemptive';
4. 执行 DDL 操作。
ALTER TABLE orders ADD COLUMN remark VARCHAR(255);
如果当前节点上存在长事务阻塞了该表的 MDL 锁,系统将在等待指定时间后自动终止阻塞会话,DDL 继续执行直至完成。
使用示例
准备工作
创建测试表并插入数据:
CREATE TABLE t1 (id INT PRIMARY KEY, name VARCHAR(50));INSERT INTO t1 VALUES (1, 'Alice'), (2, 'Bob');
未开启抢占式 DDL
会话1(模拟长事务):
BEGIN;SELECT * FROM t1;-- 事务未提交,持有 t1 的 MDL 共享读锁
会话2(执行 DDL):
ALTER TABLE t1 ADD COLUMN age INT;-- 等待 MDL 排他锁...-- 超时后报错:-- ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
结果: DDL 执行失败,需要 DBA 手动通过
performance_schema.metadata_locks 定位持锁会话。开启抢占式 DDL
会话1(模拟长事务):
BEGIN;SELECT * FROM t1;-- 事务未提交,持有 t1 的 MDL 共享读锁
会话2(开启抢占式 DDL 并执行):
SET tdsql_ddl_block_mode = 'preemptive';SET tdsql_ddl_preempt_after_wait_seconds = 5;ALTER TABLE t1 ADD COLUMN age INT;-- 等待 5 秒后,系统自动触发抢占并终止阻塞会话Query OK, 0 rows affected (5.067 sec)Records: 0 Duplicates: 0 Warnings: 0
会话1的变化:
SELECT * FROM t1;ERROR 2013 (HY000): Lost connection to MySQL server during query
结果: DDL 执行成功。会话1被终止,事务被回滚,后续操作需重新发起。
排查与诊断
当 DDL 操作遇到 MDL 锁阻塞时,您也可以通过以下方式手动排查,作为抢占式 DDL 的补充手段:
1. 查看 MDL 锁持有情况:
SELECT * FROM performance_schema.metadata_locks WHERE OBJECT_NAME = 't1';
2. 定位持锁会话并参考 PROCESSLIST_ID 手动终止持锁会话:
SELECTml.OBJECT_NAME,ml.LOCK_TYPE,ml.LOCK_STATUS,t.PROCESSLIST_ID,t.PROCESSLIST_INFOFROM performance_schema.metadata_locks mlJOIN performance_schema.threads tON ml.OWNER_THREAD_ID = t.THREAD_IDWHERE ml.OBJECT_NAME = 't1' AND ml.LOCK_STATUS = 'GRANTED';
说明:
在分布式场景下,如果 DDL 报错
ERROR 8542 ... Acquire object lock ... wait timeout,表明 DDL 被其他节点的 DDL 通过全局对象锁阻塞,此场景属于 DDL-DDL 冲突,需等待另一个 DDL 完成,抢占式 DDL 不适用于此场景。