生产事件实录-Mysql Waiting for table metadata lock

专栏链接地址

  • 1、生产事件实录-浅谈XEE漏洞
  • 2、生产事件实录-CPU报警超负载原因查找
  • 3、生产事件实录-Mysql Waiting for table metadata lock

问题起源

订单日志表4000w数据,某同事执行了下面类似一条DDL语句,因为事务过长导致用户下单时,订单插入日志的事务一直等待释放锁。服务不可用长达十分钟。
alter table gp_order_log modify column operator varchar(30);

问题分析

DDL语句执行过程分析
Mysql有一个规则,大致意思是“为确保事务可序列化,对某个表而言mysql不允许一个会话未完成时,对该表执行DDL语句。”也就是说当你对t_student表执行查询时,不允许执行alter、drop等DDL语句

MDL(metadata lock)为表级锁。MDL 不需要显式使用,在访问一个表的时候会被自动加上。MDL 的作用是,保证读写的正确性。你可以想象一下,如果一个查询正在遍历一个表中的数据,而执行期间另一个线程对这个表结构做变更,删了一列,那么查询线程拿到的结果跟表结构对不上,肯定是不行的。
因此,在 MySQL 5.5.3及更高版本中引入了 MDL使用元数据锁定来管理对对象(表、触发器等)的访问,当对一个表做增删改查操作的时候,加 MDL 读锁;当要对表做结构变更操作的时候,加 MDL 写锁。

虽然 MDL 锁是系统默认会加的,但却是你不能忽略的一个机制。比如下面这个例子
生产事件实录-Mysql Waiting for table metadata lock_第1张图片
我们可以看到 session A 先启动,这时候会对表 t 加一个 MDL 读锁。由于 session B 需要的也是 MDL 读锁,因此可以正常执行。之后 session C 会被 blocked,是因为 session A 的 MDL 读锁还没有释放,而 session C 需要 MDL 写锁,因此只能被阻塞。
如果只有 session C 自己被阻塞还没什么关系,但是之后所有要在表 t 上新申请 MDL 读锁的请求也会被 session C 阻塞。前面我们说了,所有对表的增删改查操作都需要先申请 MDL 读锁,就都被锁住,等于这个表现在完全不可读写了。

如果某个表上的查询语句频繁,而且客户端有重试机制,也就是说超时后会再起一个新 session 再请求的话,这个库的线程很快就会爆满。
以上便是导致事件发生的原因
你可能会像这样的话岂不是变成了单线程,所以mysql5.6支持Online DDL也就是在执行DDL语句时,可以对表进行读写

Online DDL的执行过程
1.拿MDL写锁
2.降级成MDL读锁
3.真正做DDL
4.升级成MDL写锁
5.释放MDL锁
1、2、4、5如果没有锁冲突,执行时间非常短,第三步占用了DDL绝大部分时间,执行第三步时是可以正常读写数据的因此成为“online”
上述例子中是DDL在执行时没有拿到MDL写锁,第一步就卡住了



如何在业务进行时执行DDL语句?

考虑一下这个场景。如果你要变更的表是一个热点表,虽然数据量不大,但是上面的请求很频繁,而你不得不加个字段,你该怎么做呢?

比较理想的解决方式就是先查询当前这表有没有正在进行的长事务,然后在 alter table 语句里面设定等待时间,如果在这个指定的等待时间里面能够拿到 MDL 写锁最好,拿不到也不要阻塞后面的业务语句,先放弃。之后开发人员或者 DBA 再通过重试命令重复这个过程。


事件处理方式

今天发生上述问题,后面解决方案如下
  • 查询正在进行的事务列表
select trx_state, trx_started, trx_mysql_thread_id, trx_query from information_schema.innodb_trx;

在这里插入图片描述
如果发生今天的案例,上图应该是第一行是RUNNING,后面的全是WAIT

  • 使用kill命令kill掉这个线程
kill trx_mysql_thread_id;

前面有说到当对一个表做增删改查操作时,会在表上加 MDL 读锁所以,session B 虽然处于 blocked 状态,但还是拿着一个 MDL 读锁的。如果线程被 kill 的时候,就直接终止,那之后这个 MDL 读锁就没机会被释放了。
所以kill 并不是马上停止的意思,而是告诉执行线程说,这条语句已经不需要继续执行了,可以开始“执行停止的逻辑了”。

类似于Thread的.strat()方法,只是告诉CPU我已经准备好了

在MySQL中有两个kill命令

  • kill query 线程ID
  • kill connection 线程ID (connection可缺省,也就是kill 线程ID)

kill query 线程ID 表示终止这个线程中正在执行的语句

kill connection 线程ID 本质上只是把客户端的sql连接断开了,后面的执行流程还是要走kill query 线程id

这里不对kill的原理做过多研究,如需要理解可以看看《MySQL实战45讲》林晓斌老师有对kill做详细的讲解

你可能感兴趣的:(数据库,生产事件实录)