第27天SQL进阶-查询优化- performance_schema系列实战三:锁问题排查(表级锁)(SQL 小虚竹)

Posted 小虚竹

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第27天SQL进阶-查询优化- performance_schema系列实战三:锁问题排查(表级锁)(SQL 小虚竹)相关的知识,希望对你有一定的参考价值。

回城传送–》《32天SQL筑基》

文章目录

零、前言

今天是学习 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_EXCLUSIVEGLOBAL对象、SCHEMA对象操作会加此锁
MDL_SHAREDFLUSH TABLES with READ LOCK
MDL_SHARED_HIGH_PRIO仅对 MyISAM 存储引擎有效
MDL_SHARED_READSELECT查询
MDL_SHARED_WRITEDML语句
MDL_SHARED_WRITE_LOW_PRIO仅对MyISAM存储引擎有效
MDL_SHARED_UPGRADABLEALTER TABLE
MDL_SHARED_READ_ONLYLOCK xxx READ
MDL_SHARED_NO_WRITEFLUSH TABLES xxx,yyy,zzz READ
MDL_SHARED_NO_READ_WRITEFLUSH TABLE xxx WRITE
MDL_EXCLUSIVEALTER 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 小虚竹)

我是虚竹哥,我们明天见~

以上是关于第27天SQL进阶-查询优化- performance_schema系列实战三:锁问题排查(表级锁)(SQL 小虚竹)的主要内容,如果未能解决你的问题,请参考以下文章

第15天SQL进阶-查询优化-慢查询日志(SQL 小虚竹)

第17天SQL进阶-查询优化- SHOW STATUS(SQL 小虚竹)

第17天SQL进阶-查询优化- SHOW STATUS(SQL 小虚竹)

第16天SQL进阶-查询优化一定要学EXPALIN (SQL 小虚竹)

第16天SQL进阶-查询优化一定要学EXPALIN (SQL 小虚竹)

第16天SQL进阶-查询优化一定要学EXPALIN (SQL 小虚竹)