数据库日常维护中我们经常遇到死锁的问题,由于无法获取造成死锁的事务内执行过的语句,对我们死锁的分析造成很大的困难。但是在MySQL 5.7中我们可以利用performance_schema来获取这些语句,为我们解决死锁问题提供了一个新的思路,下面我们将解释这种方法的使用。

一、开启相关统计的方法

如果打开performance_schema选项来收集执行过的语句和事务会有性能损失,一般建议需要的时候开启,然后在线关闭掉。

(1) 在my.cnf中设置打开、关闭performance_schema选项随数据库启动
#设置setup_instruments表收集相关statement event
performance-schema-instrument='statement/%=ON'
#开启events_statements_current表存储当前连接线程执行的最后一条statement event信息
performance-schema-consumer-events-statements-current=ON
#开启events-statements-history表默认存储每个线程最近10条statement event信息
performance-schema-consumer-events-statements-history=ON
#开启events-statements-history-long表默认存储最近10000条语句event信息
performance-schema-consumer-events-statements-history-long=ON performance-schema-consumer-statements-digest=ON #设置setup_instruments表收集transaction event
performance-schema-instrument='transaction=ON'
#开启events_transactions_current表存储当前连接线程执行的transaction event信息
performance-schema-consumer-events-transactions-current=ON
#开启events_transactions_history表默认存储每个线程最近10条transaction event信息
performance-schema-consumer-events-transactions-history=ON
#开启events_statements_history_long表默认存储最近10000条语句event信息。
performance-schema-consumer-events-transactions-history-long=ON
(2) 在my.cnf中设置关闭performance_schema选项语句和事务收集
performance-schema-instrument='statement/%=OFF'
performance-schema-consumer-events-statements-current=OFF
performance-schema-consumer-events-statements-history=OFF
performance-schema-consumer-events-statements-history-long=OFF
performance-schema-consumer-statements-digest=OFF performance-schema-instrument='transaction=OFF'
performance-schema-consumer-events-transactions-current=OFF
performance-schema-consumer-events-transactions-history=OFF
performance-schema-consumer-events-transactions-history-long=OFF
(3) 在线打开performance_schema选项收集执行过的语句和事务
update setup_consumers set ENABLED='yes' where name like 'events_statements%';
update setup_consumers set ENABLED='yes' where name like 'events_transactions%';
update setup_consumers set ENABLED='yes' where name like 'statements-digest';
update setup_instruments set ENABLED='yes',TIMED='yes' where name ='transaction';
(4) 在线关闭performance_schema选项收集执行过的语句和事务
update setup_consumers set ENABLED='no' where name like 'events_statements%';
update setup_consumers set ENABLED='no' where name like 'events_transactions%';
update setup_consumers set ENABLED='no' where name like 'statements-digest';
update setup_instruments set ENABLED='no',TIMED='no' where name ='transaction';
(5) 相关参数
  • performance_schema_events_statements_history_size:定义events_statements_history表中每个线程最大行数,静态参数,修改需要重启实例。
  • performance_schema_events_statements_history_long_size:定义events_statements_history_long表最大行数,静态参数,修改需要重启实例。
  • performance_schema_events_transactions_history_size:定义events_transactions_history表每个线程最大行数,静态参数,修改需要重启实例。
  • performance_schema_events_transactions_history_long_size:定义events_transactions_history_long表最大行数,静态参数,修改需要重启实例。

二、根据死锁信息来获取造成死锁的语句

下面是一个典型的死锁日志:

*** (1) TRANSACTION:

TRANSACTION 897551, ACTIVE 48 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 5 lock struct(s), heap size 1136, 3 row lock(s)
MySQL thread id 4, OS thread handle 140029509830400, query id 123 localhost root
statistics
select * from t1 where id=1 for update
2019-07-09T10:29:40.710270+08:00 3 [Note] InnoDB: *** (1) WAITING FOR THIS LOCK TO
BE GRANTED: RECORD LOCKS space id 591 page no 3 n bits 80 index PRIMARY of table `txc_test`.`t1`
trx id 897551 lock_mode X locks rec but not gap waiting
...... 2019-07-09T10:29:40.711199+08:00 3 [Note] InnoDB: *** (2) TRANSACTION: TRANSACTION 897550, ACTIVE 62 sec starting index read
mysql tables in use 1, locked 1
5 lock struct(s), heap size 1136, 3 row lock(s)
MySQL thread id 3, OS thread handle 140029510035200, query id 124 localhost root
statistics
select * from t2 where id=1 for update
2019-07-09T10:29:40.711364+08:00 3 [Note] InnoDB: *** (2) HOLDS THE LOCK(S): RECORD LOCKS space id 591 page no 3 n bits 80 index PRIMARY of table `txc_test`.`t1`
trx id 897550 lock_mode X locks rec but not gap
......
2019-07-09T10:29:40.712249+08:00 3 [Note] InnoDB: *** (2) WAITING FOR THIS LOCK TO
BE GRANTED: RECORD LOCKS space id 592 page no 3 n bits 80 index PRIMARY of table `txc_test`.`t2`
trx id 897550 lock_mode X locks rec but not gap waiting
......
2019-07-09T10:29:40.713153+08:00 3 [Note] InnoDB: *** WE ROLL BACK TRANSACTION (2)
(1)通过events_statements_history_long来查询死锁语句
  • 事务1

在死锁日志中我们可以看到导致死锁的最后一个语句是‘select * from t1 where id=1 for update’,那么我们就可以使用它来查询events_statements_history_long表。

mysql> select THREAD_ID,EVENT_ID,END_EVENT_ID,EVENT_NAME,SOURCE,SQL_TEXT,DIGEST,
CURRENT_SCHEMA,NESTING_EVENT_ID,NESTING_EVENT_TYPE, MESSAGE_TEXT,timer_start
from events_statements_history_long where sql_text like
'select * from t1 where id=1 for update' \G
*************************** 1. row ***************************
THREAD_ID: 37
EVENT_ID: 98
END_EVENT_ID: 106
EVENT_NAME: statement/sql/select
SOURCE: socket_connection.cc:101
SQL_TEXT: select * from t1 where id=1 for update
DIGEST: be26f0b8bee2ac2bb34e9c651d655e7c
CURRENT_SCHEMA: txc_test
NESTING_EVENT_ID: 96
NESTING_EVENT_TYPE: TRANSACTION
MESSAGE_TEXT: NULL
timer_start: 266799699801000
*************************** 2. row ***************************
THREAD_ID: 38
EVENT_ID: 35
END_EVENT_ID: 37
EVENT_NAME: statement/sql/select
SOURCE: socket_connection.cc:101
SQL_TEXT: select * from t1 where id=1 for update
DIGEST: be26f0b8bee2ac2bb34e9c651d655e7c
CURRENT_SCHEMA: txc_test
NESTING_EVENT_ID: 19
NESTING_EVENT_TYPE: TRANSACTION
MESSAGE_TEXT: NULL
  • 事务2

在死锁日志中我们可以看到导致死锁的最后一个语句是‘select * from t2 where id=1 for update’,那么我们就可以使用它来查询events_statements_history_long表。

 mysql> select THREAD_ID,EVENT_ID,END_EVENT_ID,EVENT_NAME,SOURCE,SQL_TEXT,DIGEST,
CURRENT_SCHEMA,NESTING_EVENT_ID,NESTING_EVENT_TYPE, MESSAGE_TEXT
from events_statements_history_long where sql_text like
'select * from t2 where id=1 for update' \G
*************************** 1. row ***************************
(event关联的线程id)
THREAD_ID: 38
EVENT_ID: 21
END_EVENT_ID: 29
EVENT_NAME: statement/sql/select
SOURCE: socket_connection.cc:101
(执行的sql语句信息)
SQL_TEXT: select * from t2 where id=1 for update
DIGEST: 315b4a6a8f7424bc7591256a8937a213
CURRENT_SCHEMA: txc_test
(父语句event id)
NESTING_EVENT_ID: 19
NESTING_EVENT_TYPE: TRANSACTION
MESSAGE_TEXT: NULL
(event开始时间(从实例启动到现在的时间差),单位皮秒)
timer_start: 280428752972000
*************************** 2. row ***************************
(event关联的线程id)
THREAD_ID: 37
EVENT_ID: 112
END_EVENT_ID: 114
EVENT_NAME: statement/sql/select
SOURCE: socket_connection.cc:101
(执行的sql语句信息)
SQL_TEXT: select * from t2 where id=1 for update
DIGEST: 315b4a6a8f7424bc7591256a8937a213
CURRENT_SCHEMA: txc_test
NESTING_EVENT_ID: 96
NESTING_EVENT_TYPE: TRANSACTION
MESSAGE_TEXT: Deadlock found when trying to get lock; try restarting transaction
(event开始时间(从实例启动到现在的时间差),单位皮秒)
timer_start: 328978250011000

虽然我们根据死锁日志进行了语句查询,但是我们发现没法确认哪一个是事务1,哪一个是事务2。不过我们可以通过MESSAGE_TEXT中的信息‘Deadlock found when trying to get lock; try restarting transaction’,确认THREAD_ID:37为事务2,THREAD_ID: 38为事务1的线程,因为我们的死锁日志显示事务2被回滚掉了。需要注意这里的THREAD_ID是performance_schema内部使用的,和我们平时show processlist访问的PROCESSLIST_ID不一样。它是一个performance_schema内部的计数器源码如下:

PFS_thread* create_thread(PFS_thread_class *klass, const void *identity,
ulonglong processlist_id){
PFS_thread *pfs;
pfs_dirty_state dirty_state;
pfs= global_thread_container.allocate(& dirty_state); if (pfs != NULL)
{
pfs->m_thread_internal_id=
PFS_atomic::add_u64(&thread_internal_id_counter.m_u64, 1);
pfs->m_parent_thread_internal_id= 0;
pfs->m_processlist_id= static_cast<ulong>(processlist_id);
(2)提取信息
  • 事务1

最后一条语句的END_EVENT_ID=37

最后一条语句的NESTING_EVENT_ID=19

THREAD_ID=38

因此我们可以通过如下语句得出经历的所有语句如下:

 mysql> select THREAD_ID,EVENT_ID,END_EVENT_ID,EVENT_NAME,SOURCE,SQL_TEXT,DIGEST,
CURRENT_SCHEMA,NESTING_EVENT_ID,NESTING_EVENT_TYPE, MESSAGE_TEXT,timer_start
from events_statements_history_long where thread_id=38 and END_EVENT_ID>=19 and
END_EVENT_ID <=37 \G *************************** 1. row ***************************
THREAD_ID: 38
EVENT_ID: 18
END_EVENT_ID: 19
EVENT_NAME: statement/sql/begin
SOURCE: socket_connection.cc:101
SQL_TEXT: begin
DIGEST: f57daa74a09445d1e1c496f28fe6d906
CURRENT_SCHEMA: txc_test
NESTING_EVENT_ID: NULL
NESTING_EVENT_TYPE: NULL
MESSAGE_TEXT: NULL
timer_start: 274749880798000
*************************** 2. row ***************************
THREAD_ID: 38
EVENT_ID: 21
END_EVENT_ID: 29
EVENT_NAME: statement/sql/select
SOURCE: socket_connection.cc:101
SQL_TEXT: select * from t2 where id=1 for update
DIGEST: 315b4a6a8f7424bc7591256a8937a213
CURRENT_SCHEMA: txc_test
NESTING_EVENT_ID: 19
NESTING_EVENT_TYPE: TRANSACTION
MESSAGE_TEXT: NULL
timer_start: 280428752972000
*************************** 3. row ***************************
THREAD_ID: 38
EVENT_ID: 31
END_EVENT_ID: 33
EVENT_NAME: statement/sql/select
SOURCE: socket_connection.cc:101
SQL_TEXT: select * from t1 where id=3 for update
DIGEST: be26f0b8bee2ac2bb34e9c651d655e7c
CURRENT_SCHEMA: txc_test
NESTING_EVENT_ID: 19
NESTING_EVENT_TYPE: TRANSACTION
MESSAGE_TEXT: NULL
timer_start: 296208754204000
*************************** 4. row ***************************
THREAD_ID: 38
EVENT_ID: 35
END_EVENT_ID: 37
EVENT_NAME: statement/sql/select
SOURCE: socket_connection.cc:101
SQL_TEXT: select * from t1 where id=1 for update
DIGEST: be26f0b8bee2ac2bb34e9c651d655e7c
CURRENT_SCHEMA: txc_test
NESTING_EVENT_ID: 19
NESTING_EVENT_TYPE: TRANSACTION
MESSAGE_TEXT: NULL
timer_start: 319574947823000
4 rows in set (0.00 sec)
  • 事务2

最后一条语句的END_EVENT_ID=114

最后一条语句的NESTING_EVENT_ID=96

THREAD_ID=37

因此我们可以通过如下语句得出经历的所有语句如下:

mysql> select THREAD_ID,EVENT_ID,END_EVENT_ID,EVENT_NAME,SOURCE,SQL_TEXT,DIGEST,
CURRENT_SCHEMA,NESTING_EVENT_ID,NESTING_EVENT_TYPE,MESSAGE_TEXT,timer_start
from events_statements_history_long where thread_id = 37 and END_EVENT_ID >= 96
and END_EVENT_ID <= 114\G
*************************** 1. row ***************************
THREAD_ID: 37
EVENT_ID: 95
END_EVENT_ID: 96
EVENT_NAME: statement/sql/begin
SOURCE: socket_connection.cc:101
SQL_TEXT: begin
DIGEST: f57daa74a09445d1e1c496f28fe6d906
CURRENT_SCHEMA: txc_test
NESTING_EVENT_ID: NULL
NESTING_EVENT_TYPE: NULL
MESSAGE_TEXT: NULL
timer_start: 251219441701000
*************************** 2. row ***************************
THREAD_ID: 37
EVENT_ID: 98
END_EVENT_ID: 106
EVENT_NAME: statement/sql/select
SOURCE: socket_connection.cc:101
SQL_TEXT: select * from t1 where id=1 for update
DIGEST: be26f0b8bee2ac2bb34e9c651d655e7c
CURRENT_SCHEMA: txc_test
NESTING_EVENT_ID: 96
NESTING_EVENT_TYPE: TRANSACTION
MESSAGE_TEXT: NULL
timer_start: 266799699801000
*************************** 3. row ***************************
THREAD_ID: 37
EVENT_ID: 108
END_EVENT_ID: 110
EVENT_NAME: statement/sql/select
SOURCE: socket_connection.cc:101
SQL_TEXT: select * from t2 where id=6 for update
DIGEST: 315b4a6a8f7424bc7591256a8937a213
CURRENT_SCHEMA: txc_test
NESTING_EVENT_ID: 96
NESTING_EVENT_TYPE: TRANSACTION
MESSAGE_TEXT: NULL
timer_start: 308095739676000
*************************** 4. row ***************************
THREAD_ID: 37
EVENT_ID: 112
END_EVENT_ID: 114
EVENT_NAME: statement/sql/select
SOURCE: socket_connection.cc:101
SQL_TEXT: select * from t2 where id=1 for update
DIGEST: 315b4a6a8f7424bc7591256a8937a213
CURRENT_SCHEMA: txc_test
NESTING_EVENT_ID: 96
NESTING_EVENT_TYPE: TRANSACTION
MESSAGE_TEXT: Deadlock found when trying to get lock; try restarting transaction
timer_start: 328978250011000
4 rows in set (0.01 sec)
(3)猜测死锁形成过程

根据上面两个事务执行的sql大致可以推断出死锁的sql语句,如果两个事务里面执行的sql很多那可能就需要花更多的时间来找出造成死锁的语句:

TX1 TX2
  select * from t1 where id=1 for update;
select * from t2 where id=1 for update;  
select * from t1 where id=3 for update;  
  select * from t2 where id=6 for update;
select * from t1 where id=1 for update;  
  select * from t2 where id=1 for update;

三、总结

  • 通过以上的查询基本可以获取造成死锁的事务内执行的语句,由于线上业务量大可能造成events_statements_history_long查询不到需要的语句(默认存储10000条),需要及时监控发现死锁。
  • 打开performance_schema的选项,有性能损失。
  • 如果线上实例是每个database对应一个独立用户,可以通过设置收集指定用户执行的所有event。

    1).关闭收集所有用户的event

    update setup_actors set ENABLED='NO',HISTORY='NO';

    2).插入需要收集event的指定用户(例如我只想收集txc用户下的所有event,参考如下)

    insert into setup_actors select '%','txc','%','YES','YES';

    3).select * from setup_actors;
         +------+------+------+---------+---------+
| HOST | USER | ROLE | ENABLED | HISTORY |
+------+------+------+---------+---------+
| % | % | % | NO | NO |
| % | txc | % | YES | YES |
+------+------+------+---------+---------+
 

作者:重庆八怪
链接:https://www.jianshu.com/p/268889c997e8
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

通过performance_schema获取造成死锁的事务语句(转)的更多相关文章

  1. C# 最基本的涉及模式(单例模式) C#种死锁:事务(进程 ID 112)与另一个进程被死锁在 锁 | 通信缓冲区 资源上,并且已被选作死锁牺牲品。请重新运行该事务,解决方案: C#关闭应用程序时如何关闭子线程 C#中 ThreadStart和ParameterizedThreadStart区别

    C# 最基本的涉及模式(单例模式) //密封,保证不能继承 public sealed class Xiaohouye    { //私有的构造函数,保证外部不能实例化        private  ...

  2. Sql Server 检测死锁的SQL语句

    首先创建一个标量值函数DigLock,用来递归检测SqlServer中的每一个会话是否存在加锁循环,如果该函数最终返回1则表示检测到了加锁循环 (也就是说检测到了死锁),如果最终返回0则表示没有检测到 ...

  3. 【转载】 Sqlserver查看数据库死锁的SQL语句

    在Sqlsever数据库中,有时候操作数据库过程中会进行锁表操作,在锁表操作的过程中,有时候会出现死锁的情况出现,这时候可以使用SQL语句来查询数据库死锁情况,主要通过系统数据库Master数据库来查 ...

  4. laravel 获取上一条insert语句产生的id

    <?php //頭部引入DB類 use Illuminate\Support\Facades\DB; //在方法中獲取获取上一条insert语句产生的id $id = DB::getPdo()- ...

  5. 【Navicat】获取表结构的DDL语句以及获取更新表字段的操作的DDL

    1.获取表结构的DDL语句 2.获取修改表结构某一字段的DDL语句  设计表-修改表字段(记住不要保存)-SQL预览

  6. sql 批处理、获取自增长、事务、大文本处理

    批处理 需要批量执行sql语句! 需求:批量保存信息! 设计: AdminDao Public void save(List<Admin list){ // 目前用这种方式 // 循环 // 保 ...

  7. 读写分离,读写分离死锁解决方案,事务发布死锁解决方案,发布订阅死锁解决方案|事务(进程 ID *)与另一个进程被死锁在 锁 资源上,并且已被选作死锁牺牲品。请重新运行该事务

    前言:         由于网站访问压力的问题,综合分析各种因素后结合实际情况,采用数据库读写分离模式来解决当前问题.实际方案中采用“事务发布”模式实现主数据库和只读数据库的同步,其中: 发布服务器1 ...

  8. PB中获取datawindow提交的sql语句

    PB的群里边,有人问的到这个问题,查了一下,综合了两条回答,得到了答案 1.DW 控件的SQLpreview 事件里的sqlsyntax 参数即是 2.pb一般使用占位符优化SQL语句,也就是你看到的 ...

  9. java反射获取注解并拼接sql语句

    先建两个注解 分别为 Table 和 Column package com.hk.test; import java.lang.annotation.ElementType; import java. ...

  10. 获取linq生成的sql语句

    命名空间:using System.Data.Objects; var query = db.TxtRes.Join(db.LangRes, a => new { id1 = a.ResID, ...

随机推荐

  1. [转帖]Linux中awk命令正确的求最大值、最小值、平均值、总和

    https://blog.csdn.net/fireblue1990/article/details/51622416 test.txt文件内容: 9 11 35 21 42 118 求最大值: aw ...

  2. Linux上面Shell简单进行数值计算的办法

    1. 自己简单写了一个脚本 来计算 一个服务进程启动的耗时, 精度要求不高 10秒上下就可以. 在程序执行之前和之后的处理 在最前面设置一句time1=`date +%s`在末尾一句设置time2=` ...

  3. Oracle表数量对数据泵备份恢复速度的影响情况

    Oracle表数量对数据泵备份恢复速度的影响情况 背景 随着公司产品交付后的时间越来越久. 数据库的备份恢复速度会越来越慢. 最开始一直认为是因为数据量导致的. 但是最近发现, 如果只是将数据库表的量 ...

  4. uni-app 顶部配置搜索框和左右图标

    顶部的图标只支持本地图片哈,所以你要将阿里巴巴上的图标下载到本地, 然后只要XXX.ttf这个文件就行了 然后放在static这个文件中 在pages.json中fontSrc进行引入. text:使 ...

  5. Fabric-sdk-go操作Chaincode

    因为工作的需要,最近了解了下如何通过sdk来操作Chaincode,本文是sdk使用时的一些操作总结. 在fabric网络启动过程中,一般分为"启动网络 -> 创建通道 -> 加 ...

  6. 从零开始配置vim(25)——关于 c++ python 的配置

    从9月份到国庆这段时间,因为得了女儿,于是回老家帮忙料理家事以及陪伴老婆和女儿.一时之间无暇顾及该系列教程的更新.等我回来的时候发现很多小伙伴私信我催更.在这里向支持本人这一拙劣教程的各位小伙伴表示真 ...

  7. 通过docker-compose搭建mongo的replica set高可用

    通过docker-compose搭建mongo的replica set高可用 前言 备份数据 备份数据到本地 数据恢复 集群搭建 生成keyFile 创建yml文件 初始化副本集 增加副本集 将节点初 ...

  8. LyScript 验证PE程序开启的保护

    有些漏洞利用代码需要在某个保护模式被关闭的情况下才可以利用成功,在此之前需要得到程序开启了何种保护方式.验证其实有很多方法,其原理是读入PE文件头部结构,找到OPTIONAL_HEADER.DllCh ...

  9. Win32汇编:仿写多态与虚函数

    多态性是面向对象的重要组成部分,利用多态可以设计和实现易于扩展的程序,所谓多态就是一个类函数有多重形态,具有不同功能的函数可以用同一个函数名,实现使用一个函数名调用不同内容的函数,从而返回不同的结果, ...

  10. Linux下开发基于.NET的三维绘图程序

    很多人可能知道使用.NET Core可以开发跨平台(包括Windows,Linux.MacOS)的App,但知道在Linux下使用.NET Core可以开发三维程序的恐怕就很少了.本文通过借助.NET ...