用一个未提交的普通SELECT事务阻塞ALTER TABLE,再验证ALTER排队之后才发起的新SELECT查询是否会被一起卡住。
哪怕只是读,一个未提交的事务也不会轻易放手。
代码A: START TRANSACTION A: SELECT * FROM t12 WHERE id=1 -- 只是普通读,不加FOR UPDATE (A不提交,事务保持开启)
A没有锁任何行(没用FOR UPDATE),但依然挡住了DDL。
结果B: ALTER TABLE t12 ADD COLUMN extra INT 1.5秒后Acommit,B才完成 B耗时: 1.5099 s
原因是:只要一个事务对某张表执行过任何语句(哪怕只是普通SELECT),这个事务就持有这张表的一个元数据锁(MDL),一直到事务提交或回滚才释放。ALTER TABLE 需要获取这张表的排他型MDL才能真正开始改结构,只要还有事务攥着共享型的读MDL不放,ALTER就只能排队等。
这是最容易被忽视、也最容易造成生产事故的一点。
时间线t=0s A开启事务,SELECT一行(占着共享MDL) t=0.5s B发起ALTER TABLE,开始排队等待 t=1.5s C才发起一条普通SELECT t=1.5s Acommit 结果: B(ALTER TABLE)耗时: 1.5099 s C(普通SELECT,在B排队之后才发起)耗时: 1.0074 s ← 也被卡住了!
C的这条SELECT是在B的ALTER已经开始排队等待之后才发起的,理论上C只是要读,跟B的ALTER互不冲突——但结果是C也被卡住了整整1.0074秒(从C发起到A commit的时间差),直到A提交释放锁,B和C才一起被放行。
每一步都是本文上面用三个MySQL连接跑出来的原始结果。