在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
遇到故障,我们往往想的是如何解决这个故障,而不是从故障的根本去思考出现这个故障的原因?这样的结果,只能使我们得到了鱼,失去了渔。今天,我们就来分享一个由USE DB堵塞故障引发的思考案例。 故障描述 今天一个朋友遇到数据库遇到一个严重的故障,故障环境如下: MYSQL 5.6.16 RR隔离级别 GITD关闭 表现如下: use db不能进入数据库 show table status不能查询到表信息 schema.processlist来看有大量的 Waiting for table metadata lock 情急之下他杀掉了一大堆线程后发现还是不能恢复,最后杀掉了一个没有及时提交的事物才恢复正常。也仅仅留下了如下图的一个截图: 故障信息提取 还是回到上图,我们可以归纳一下语句类型如下: 1、CREATE TABLE A AS SELECT B 其STATE为 sending data 2、DROP TABLE A 其STATE为 Waiting for table metadata lock 3、SELECT * FROM A 其STATE为 Waiting for table metadata lock 4、 SHOW TABLE STATUS[like 'A'] 其STATE为 Waiting for table metadata lock 信息分析 要分析出这个案列其实不太容易因为他是MYSQL层MDL LOCK和RR模式innodb row lock的一个综合案列,并且我们要对schema.processlist的STATE比较敏感才行。 建议先阅读我的如下文章来学习MDL LOCK: https://www.ogeek.net/article/131383.htm 本节关于MDL LOCK的验证使用下面两种方式: 方式一:笔者在MDL LOCK源码加锁函数处加日志输出,如果要分析各种语句加MDL LOCK的类型还只能用这种方式,因为MDL LOCK加锁往往一闪而过,performance_schema.metadata_locks 没有办法观察到。 方式二:处于堵塞情况下使用5.7版本的performance_schema.metadata_locks观察。 在P_S中打开mdl监测方法如下: 一、关于CREATE TABLE A AS SELECT B 对B表sending data的分析 关于sending data这个状态其实可以代表很多含义,从我现有的对的了解,这是MYSQL上层对SELECT类型语句的这类语句在INNODB层和MYSQL层进行数据交互的时候一个统称,所以出现它的可能包含: 确实需要访问数据量特别大,可能需要优化。 由于INNODB 层的获取row lock需要等待,比如我们常见的SELECT FOR UPDATE。 同时我们还需要注意在RR模式下SELECT B这一部分加锁方式和INSERT...SELECT是一致的参考不再赘述: 从他反应的情况因为他在最后杀掉了一个长期的未提交的事物所以他因为是情况2。并且整个CREATE TABLE A AS SELECT B语句由于B表上某些数据库被上了锁而不能获取,导致整个语句处于sending data状态下。 二、关于SHOW TABLE STATUS[like 'A'] Waiting for table metadata lock的分析 这是本案例中最重要的一环,SHOW TABLE STATUS[like 'A']居然被堵塞其STATE为Waiting for table metadata lock并且注意这里是table因为MDL LOCK类型分为很多。我在MDL介绍的那篇文章中提到了desc 一个表的时候会上MDL_SHARED_HIGH_PRIO(SH),其实在SHOW TABLE STATUS的时候也会对本表上MDL_SHARED_HIGH_PRIO(SH)。 方式一 方式二 两种方式都能观察到MDL_SHARED_HIGH_PRIO(SH)的存在并且我模拟的是处于堵塞情况下的。 但是MDL_SHARED_HIGH_PRIO(SH) 是一个优先级非常高的一个MDL LOCK类型表现如下: 兼容性: 阻塞队列优先级: 其被堵塞的条件除了被MDL_EXCLUSIVE(X)堵塞没有其他的可能。那么这就是一个非常重要的突破口。 三、关于CREATE TABLE A AS SELECT B 对A表的加MDL LOCK的分析 这一点也是我以前不知道的,也是本案列中花时间最多的地方,前文已经分析过要让SHOW TABLE STATUS[like 'A']这种只会上MDL_SHARED_HIGH_PRIO(SH) MDL LOCK的语句堵塞在MDL LOCK上只有一种可能那就是A表上了MDL_EXCLUSIVE(X)。 那么我开始怀疑这个DDL语句在语句结束之前会对A表上MDL_EXCLUSIVE(X) ,然后进行实际测试不出所料确实是这样的如下: 方式一 方式二 这里比较遗憾在performance_schema.metadata_locks中并没有显示出MDL_EXCLUSIVE(X),而显示为MDL_SHARED(S)是我们在我输出的日志中可以看到这里做了升级操作将MDL_SHARED(S) 升级为了MDL_EXCLUSIVE(X)。并且由前面的兼容性列表来看,只有MDL_EXCLUSIVE(X)会堵塞MDL_SHARED_HIGH_PRIO(SH)。所以我们应该能够确认这里确实做了升级操作,否则SHOW TABLE STATUS[like 'A'] 是不会被堵塞的。 四、关于SELECT * FROM A Waiting for table metadata lock的分析 也许大家认为SELECT不会上锁,但是那是在innodb 层次,在MYSQL层会上MDL_SHARED_READ(SR) 如下: 方式一 方式二 可以看到确实有MDL_SHARED_READ(SR)的存在,当前处于堵塞状态 其兼容性如下: 显然MDL_SHARED_READ(SR) 和MDL_SHARED_HIGH_PRIO(SH)是不兼容的需要等待。 五、关于DROP TABLE A Waiting for table metadata lock的分析 这一点很好分析因为A表上了X锁而DROP TABLE A必然上MDL_EXCLUSIVE(X)锁它当然和MDL_EXCLUSIVE(X)不兼容。如下: 方式一 方式二 其中EXCLUSIVE就是我们说的MDL_EXCLUSIVE(X)它确实存在当前处于堵塞 六、为何use db也会堵塞? 如果使用mysql客户端不使用-A选项(或者 no-auto-rehash)在USE DB的时候至少要做如下事情: 1、 对db下每个表上MDL (SH) lock如下(调用MDL_context::acquire_lock 这里给出堵塞时候的信息) 方式一 方式二 可以看到USE DB确实也因为MDL_SHARED_HIGH_PRIO(SH) 发生了堵塞。 2、对每个表加入到table cache,并且打开表(调用open_table_from_share()) 那么这种情况就和SHOW TABLE STATUS[like 'A']被堵塞的情况一模一样了,也是由于MDL 锁不兼容造成的。 分析梳理 有了前面的分析那么我们可以梳理这个故障发生的原因如下: 有一个在B表上长期未提交的DML 由步骤1引起了CREATE TABLE A AS SELECT B的堵塞 由步骤2引起了其他语句的堵塞 模拟测试 测试环境: 5.7.14 GITD关闭 RR隔离级别 使用脚本: 步骤如下: session1 | session2 | session3 | session4------use test;---use test;begin; delete from b;------------use test;create table a asselect * from b;(由于b表innodb row lock堵塞)------------show table status like 'a';(由于a表MDL LOCK堵塞)------------use test(由于a表MDL LOCK堵塞) |
---|
请发表评论