回城传送–》《32天SQL筑基》
文章目录
- 零、前言
- 一、什么是表级锁
- 二、什么时候适合加表级锁
- 三、实战演练
- 3.1 数据准备(如果已有数据可跳过此操作)
- 3.2 开启第一个会话,执行显式加表级锁
- 3.3 开启第二个会话,对该表执行update更新
- 3.4 开启第三个会话,查询线程信息
- 3.5 分析
- 3.6 释放第一个会话的表读锁
- 四、总结
- 五、参考
零、前言
今天是学习 SQL 打卡的第 27 天,每天我会提供一篇文章供群成员阅读( 不需要订阅付钱 )。
希望大家先自己思考,如果实在没有想法,再看下面的解题思路,自己再实现一遍。在小虚竹JAVA社区 中对应的 【打卡贴】打卡,今天的任务就算完成了,养成每天学习打卡的好习惯。
虚竹哥会组织大家一起学习同一篇文章,所以有什么问题都可以在群里问,群里的小伙伴可以迅速地帮到你,一个人可以走得很快,一群人可以走得很远,有一起学习交流的战友,是多么幸运的事情。
我的学习策略很简单,题海策略+ 费曼学习法。如果能把这些题都认认真真自己实现一遍,那意味着 SQL 已经筑基成功了。后面的进阶学习,可以继续跟着我,一起走向架构师之路。
今天的学习内容是:SQL进阶-查询优化- performance_schema系列实战二:锁问题排查(表级锁)
一、什么是表级锁
表级锁是MySQL中对当前操作的整张表加锁,它实现简单,资源消耗较少,被大部分MySQL引擎支持。最常使用的MYISAM与INNODB都支持表级锁定。表级锁定分为表共享读锁(共享锁)与表独占写锁(排他锁)。
特点:开销小,加锁快;不会出现死锁;锁定粒度大,发出锁冲突的概率最高,并发度最低。
二、什么时候适合加表级锁
1、事务需要更新某张大表的大部分或全部数据。如果使用默认的行锁,不仅事务执行效率低,而且可能造成其它事务长时间锁等待和锁冲突,这种情况下可以考虑使用表锁来提高事务执行速度;
2、事务涉及多个表,比较复杂,可能会引起死锁,导致大量事务回滚,可以考虑表锁避免死锁。
3、表级锁更适合于以查询为主,并发用户少,只有少量按索引条件更新数据的应用,如Web 应用
其中表锁又分为表读锁和表写锁,命令分别是:
表读锁:
lock tables 表名 read;
表写锁:
lock tables 表名 write;
解锁:释放锁只能释放我们手动使用LOCK 加的锁, 不能释放死锁等其他情况的事物锁等
UNLOCK TABLES;
三、实战演练
通过一个示例演示如何找出谁持有表级锁。
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 开启第一个会话,执行显式加表级锁
登录mysql数据库
use sysbenchdemo;
查询以下加锁线程的process id,以便后续排查过程好对应
select connection_id();
对sbtest1 表加表读锁
lock table sbtest1 read;
3.3 开启第二个会话,对该表执行update更新
登录mysql数据库
use sysbenchdemo;
查询线程的process id,以便后续排查过程好对应
select connection_id();
对该表执行update更新
update sbtest1 set pad='xxx' where id=1;
发现执行更新语句被阻塞。
3.4 开启第三个会话,查询线程信息
show processlist;
可以发现update语句在等待MDL锁(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 分析
因为是等待MDL锁,所以先看看performance_schema.metadata_locks表,记录的顺序代表持有锁的时间顺序
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_READ_ONLY的12606 内部线程手动加了表读锁;
确认一下线程是否存在着一个没有提交的事务。
select * from information_schema.innodb_trx;
发现没有记录。
可以尝试着去查询一些表级别的锁信息(通过会话3查询performance_schema.table_handles表)
select * from performance_schema.table_handles where OWNER_THREAD_ID=12606;
证实了12606 内部线程手动加了表读锁;但还是不知道该线程执行了什么SQL语句,可以通过performance_schema.events_statements_current表查询。
select * from performance_schema.events_statements_current where thread_id=12606;
通过SQL_TEXT字段我们可以清晰地看到该线程正在执行的SQL语句是什么。
3.6 释放第一个会话的表读锁
UNLOCK TABLES;
第二个会话被阻塞的语句也顺利执行了。
在第三个会话中查看线程情况
show processlist;
从图上结果可知,MDL锁被释放了。
四、总结
通过本文学习,学会了什么是表级锁以及表级锁的适用场景,通过实战演练排查表级锁问题,从理论到实战的介绍,可以加深对表级锁的理解。
五、参考
应用示例荟萃 | performance_schema全方位介绍(中)
技术分享 | MySQL 的 MDL 锁解惑
SQL进阶-查询优化- performance_schema系列三:事件记录(SQL 小虚竹)
我是虚竹哥,我们明天见~