回城传送–》《32天SQL筑基》
文章目录
- 零、前言
- 一、什么是MDL锁
- 二、什么时候适合加MDL锁
- 三、 实战演练
- 3.1 数据准备(如果已有数据可跳过此操作)
- 3.2 开启第一个会话,显式开启一个事务,并执行一个update语句不提交
- 3.3 开启第二个会话,对sbtest1表执行DDL语句添加一个普通索引
- 3.4 开启第三个会话,查询线程信息
- 3.5 分析
- 3.6 提交第一个会话的事务
- 四、总结
- 五、参考
零、前言
今天是学习 SQL 打卡的第 26 天,每天我会提供一篇文章供群成员阅读( 不需要订阅付钱 )。
希望大家先自己思考,如果实在没有想法,再看下面的解题思路,自己再实现一遍。在小虚竹JAVA社区 中对应的 【打卡贴】打卡,今天的任务就算完成了,养成每天学习打卡的好习惯。
虚竹哥会组织大家一起学习同一篇文章,所以有什么问题都可以在群里问,群里的小伙伴可以迅速地帮到你,一个人可以走得很快,一群人可以走得很远,有一起学习交流的战友,是多么幸运的事情。
我的学习策略很简单,题海策略+ 费曼学习法。如果能把这些题都认认真真自己实现一遍,那意味着 SQL 已经筑基成功了。后面的进阶学习,可以继续跟着我,一起走向架构师之路。
今天的学习内容是:SQL进阶-查询优化- performance_schema系列实战二:锁问题排查(MDL锁)
一、什么是MDL锁
表级锁有两种,一种是针对于表记录数据的锁,另外一种就是MDL(metadata lock)的锁,它是基于表元数据(表结构)的锁,MDL锁是为了保证并发环境下元数据和表数据的结构一致性。如果有其它事务对表加了MDL锁,那么其它事务就不能对表结构进行变更,同样对于正在进行表结构变更的时候也不允许其它事务对表数据进行增删改查。
二、什么时候适合加MDL锁
MDL读锁:在我们对表数据进行增删改查的的时候都需要对表加MDL读锁。
MDL写锁:当我们对表结构进行修改的时候会加MDL写锁。
三、 实战演练
当使用show processlist; 语句查看线程时,发现State列为“Waiting for table metadata lock” ,这种情况就是要去排查谁持有了MDL锁没释放。
下面我们尝试着进行MDL锁等待场景模拟(mdl锁记录对应的instruments为wait/lock/metadata/sql/mdl,默认未启用,对应的consumers为performance_schema.metadata_locks,在setup_consumers只受全局配置项global_instrumentation控制,默认启用)。
3.1 数据准备(如果已有数据可跳过此操作)
使用sysbench准备初始化数据
创建测试数据库sysbenchdemo
create database sysbenchdemo;
准备测试数据:
sysbench /usr/share/sysbench/oltp_insert.lua \
--mysql-host=localhost \
--mysql-port=3306 \
--mysql-socket=/tmp/mysql.sock \
--mysql-user=root \
--mysql-password=xiaoxuzhu \
--mysql-db=sysbenchdemo \
--db-driver=mysql \
--tables=8 \
--table-size=100000 \
--time=180 prepare
3.2 开启第一个会话,显式开启一个事务,并执行一个update语句不提交
登录mysql数据库
use sysbenchdemo;
查询以下加锁线程的process id,以便后续排查过程好对应
select connection_id();
开启事务
begin;
执行一个update语句,不提交
select * from sbtest1 limit 1;
update sbtest1 set pad='yyy' where id=1;
3.3 开启第二个会话,对sbtest1表执行DDL语句添加一个普通索引
登录mysql数据库
use sysbenchdemo;
查询线程的process id,以便后续排查过程好对应
select connection_id();
alter table sbtest1 add index index_c(c);
发现执行ddl语句被阻塞了。
3.4 开启第三个会话,查询线程信息
show processlist;
从Info 字段知道是哪个语句出了问题。
可发现刚才执行的添加索引的语句,被阻塞住。
Waiting for table metadata lock
其中OWNER_THREAD_ID: # 持有锁的内部线程ID
查看process id为 12442,12443 各自对应的内部线程ID是多少
select sys.ps_thread_id(12442);
process id=12442 的线程对应的内部线程ID正好为12606
select sys.ps_thread_id(12443);
process id=12443 的线程对应的内部线程ID正好为12607
3.5 分析
通过查询performance_schema.metadata_locks表得知MDL锁信息
select * from performance_schema.metadata_locks where OWNER_THREAD_ID!=sys.ps_thread_id(connection_id());
如下就是MDL相关的锁模式,以及对应的SQL语句
锁模式 | 对应SQL |
---|---|
MDL_INTENTION_EXCLUSIVE | GLOBAL对象、SCHEMA对象操作会加此锁 |
MDL_SHARED | FLUSH TABLES with READ LOCK |
MDL_SHARED_HIGH_PRIO | 仅对 MyISAM 存储引擎有效 |
MDL_SHARED_READ | SELECT查询 |
MDL_SHARED_WRITE | DML语句 |
MDL_SHARED_WRITE_LOW_PRIO | 仅对MyISAM存储引擎有效 |
MDL_SHARED_UPGRADABLE | ALTER TABLE |
MDL_SHARED_READ_ONLY | LOCK xxx READ |
MDL_SHARED_NO_WRITE | FLUSH TABLES xxx,yyy,zzz READ |
MDL_SHARED_NO_READ_WRITE | FLUSH TABLE xxx WRITE |
MDL_EXCLUSIVE | ALTER TABLE xxx PARTITION BY … |
从图上可知,LOCK_TYPE 为SHARED_WRITE的12606 内部线程持有MDL锁。
有sysbenchdemo.sbtest1表的SHARED_UPGRADABLE、EXCLUSIVE锁,其中SHARED_UPGRADABLE处于GRANTED状态,EXCLUSIVE处于PENDING状态
说明内部线程12607 在等待MDL锁。
通过show processlist语句的查询结果
可以看出process id为12442 的线程已经长时间处于sleep状态。但不知道执行了什么语句。
确认一下线程是否存在着一个没有提交的事务。
select * from information_schema.innodb_trx;
从查询结果可知,确实这个process id为12442 的线程有一个没有提交的事务,但还是不知道执行了什么语句。
临时解决方案:kill 12442 的线程
永久解决方案还是要找出执行了什么语句。
通过performance_schema.events_statements_current表来查询某个线程正在执行或者说最后一次执行完成的语句事件信息。
select * from performance_schema.events_statements_current where thread_id=12606;
通过SQL_TEXT字段我们可以清晰地看到该线程正在执行的SQL语句是什么。
注意:performance_schema.events_statements_current表的信息不一定可靠,因为该表中对于每个线程只能记录当前正在执行和最近一次执行完成的语句事件信息,一旦这个线程执行新的语句,信息就会被覆盖。
要保证是最可靠的,可以去查看events_statements_history 表,events_statements_history表包含每个线程最新的N个语句事件。
select * from performance_schema.events_statements_history where thread_id=12606;
3.6 提交第一个会话的事务
commit;
第二个会话被阻塞的语句也顺利执行了。
在第三个会话中查看线程情况
show processlist;
从图上结果可知,MDL锁被释放了。
四、总结
通过本文学习,学会了什么是MDL锁以及MDL锁的适用场景,通过实战演练排查MDL锁问题,从理论到实战的介绍,可以加深对MDL锁的理解。
五、参考
应用示例荟萃 | performance_schema全方位介绍(上)
技术分享 | MySQL 的 MDL 锁解惑
SQL进阶-查询优化- performance_schema系列三:事件记录(SQL 小虚竹)
我是虚竹哥,我们明天见~