请教一个MYSQL中死锁的问题

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了请教一个MYSQL中死锁的问题相关的知识,希望对你有一定的参考价值。

通过代码解锁。

代码如下    

1set global max_connections=4000;

增加允许的最大连接数,先让前台网站可以正常工作。

回过头google :mysql unauthenticated user

果然,遇到此类问题的人很多,问题在于mysql的反向ip地址解析,配置参数里加上skip-name-resolve就可以。

补充


一、查看进程运行情况(会话1)

代码如下    

1mysql> select id,user,host,db,command,time,state from processlist a;+—-+——+—————–+——————–+———+——+———–+| id | user | host | db | command | time | state|+—-+——+—————–+——————–+———+——+———–+| 40 | root | localhost:14046 | information_schema | Query | 0 | executing|| 39 | root | localhost:13992 | chf | Sleep | 251 ||| 38 | root | localhost:13991 | chf | Sleep | 251 ||+—-+——+—————–+——————–+———+——+———–+3 rows in set (0.00 sec)

二、构造表被锁现象
1)锁住表(会话1)

代码如下    

1mysql>LOCK TABLES chf.disc02 READ;或者–LOCK TABLES chf.disc02 WRITE;

2)执行dml操作(会话2)

代码如下    

1mysql>delete from chf.disc02 limit 1;–会话处于卡死状态

3)查询进程运行情况(会话1)

代码如下    

1mysql> select id,user,host,db,command,time,state from processlist a;+—-+——+—————–+——————–+———+——+———–+| id | user | host | db | command | time | state|+—-+——+—————–+——————–+———+——+———–+| 41 | root | localhost:14358 | chf | Query | 5 | Locked|| 40 | root | localhost:14046 | information_schema | Query | 0 | executing|| 39 | root | localhost:13992 | chf | Sleep | 343 ||| 38 | root | localhost:13991 | chf | Sleep | 343 ||+—-+——+—————–+——————–+———+——+———–+

4 rows in set (0.01 sec)
说明:发现进程id为41的进程状态为Locked
三、解锁操作
1)删掉被锁进程(会话1)

代码如下    

1mysql> kill 41;

出现现象(会话2)
ERROR 2013 (HY000): Lost connection to MySQL server during query
2)查看进程(会话1)

代码如下    

1mysql> select id,user,host,db,command,time,state from processlist a;+—-+——+—————–+——————–+———+——+———–+| id | user | host | db | command | time | state|+—-+——+—————–+——————–+———+——+———–+| 40 | root | localhost:14046 | information_schema | Query | 0 | executing|| 39 | root | localhost:13992 | chf | Sleep | 298 ||| 38 | root | localhost:13991 | chf | Sleep | 298 ||+—-+——+—————–+——————–+———+——+———–+3 rows in set (0.01 sec)

四、批量解锁

代码如下    

1mysql> select concat(‘kill ‘,id,’;') kill_process from processlist a where a.state=’Locked’;+————–+| kill_process |+————–+| kill 43; || kill 42; |+————–+2 rows in set (0.01 sec)

Note:
1)可以使用show processlist查看当前用户连接
如果是root帐号,你能看到所有用户的当前连接。如果是其它普通帐号,只能看到自己占用的连接。show processlist;只列出前100条,如果想全列出请使用show full processlist;
2)在构造锁的会话中,使用unlock tables;也可以解锁

总结一下原因,大概如下:

因为mysql默认会根据客户端的ip地址反向解析,用于用户登录授权之用。不过正常情况下,很少会有人这样用。ip地址反向解析是很慢的,尤其是高负荷的mysql,每秒种几百次甚至更高的请求,这个请求压到本地的dns服务器上,dns服务器说不定会怀疑你在恶意请求,然后不理你了,然后这些登录请求就挂在那里,后面的连接还持续,然后越积越多,然后就达到mysql的最大连接数据限制了,然后新的连接就直接被拒,得到连接数过多的消息。

因为mysql配置文件使用的之前的配置文件,当时跟web同服务器,所以不存在这个问题。

这也正好解释了为什么phpMyAdmin里看mysqld状态时,有很多失败的连接,它们应该就是因反解析失败而被拒的。

参考资料

MySQL解锁.壹聚教程[引用时间2018-1-21]

参考技术A 是不是报了
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
的错误?

如果是的话,那么应该是有别的程序,也在更新这个表。
你需要确定另外一个程序处理的顺序。
然后想办法让你的同步程序,与那个程序,错开时间运行。本回答被提问者和网友采纳
参考技术B 多个线程不是根据主键同时进行update,会造成死锁 参考技术C

加锁情况与死锁原因分析

为方便大家复现,完整表结构和数据如下:

CREATE TABLE `t3` (`c1` int(11) NOT NULL AUTO_INCREMENT,`c2` int(11) DEFAULT NULL,PRIMARY KEY (`c1`),UNIQUE KEY `c2` (`c2`)) ENGINE=InnoDBinsert into t3 values(1,1),(15,15),(20,20);


在 session1 执行 commit 的瞬间,我们会看到 session2、session3 的其中一个报死锁。这个死锁是这样产生的:

    1. session1 执行 delete  会在唯一索引 c2 的 c2 = 15 这一记录上加 X lock(也就是在MySQL 内部观测到的:X Lock but not gap);

    2. session2 和 session3 在执行 insert 的时候,由于唯一约束检测发生唯一冲突,会加 S Next-Key Lock,即对 (1,15] 这个区间加锁包括间隙,并且被 seesion1 的 X Lock 阻塞,进入等待;

    3. session1 在执行 commit 后,会释放 X Lock,session2 和 session3 都获得 S Next-Key Lock;

    4. session2 和 session3 继续执行插入操作,这个时候 INSERT INTENTION LOCK(插入意向锁)出现了,并且由于插入意向锁会被 gap 锁阻塞,所以 session2 和 session3 互相等待,造成死锁。

    死锁日志如下: 

    INSERT INTENTION LOCK

    在之前的死锁分析第四点,如果不分析插入意向锁,也是会造成死锁的,因为插入最终还是要对记录加 X Lock 的,session2 和 session3 还是会互相阻塞互相等待。

    但是插入意向锁是客观存在的,我们可以在官方手册中查到,不可忽略:

    Prior to inserting the row, a type of gap lock called an insert intention gap lock is set. This lock signals the intent to insert in such a way that multiple transactions inserting into the same index gap need not wait for each other if they are not inserting at the same position within the gap.

    插入意向锁其实是一种特殊的 gap lock,但是它不会阻塞其他锁。假设存在值为 4 和 7 的索引记录,尝试插入值 5 和 6 的两个事务在获取插入行上的排它锁之前使用插入意向锁锁定间隙,即在(4,7)上加 gap lock,但是这两个事务不会互相冲突等待。

    当插入一条记录时,会去检查当前插入位置的下一条记录上是否存在锁对象,如果下一条记录上存在锁对象,就需要判断该锁对象是否锁住了 gap。如果 gap 被锁住了,则插入意向锁与之冲突,进入等待状态(插入意向锁之间并不互斥)。总结一下这把锁的属性:

    1. 它不会阻塞其他任何锁;

    2. 它本身仅会被 gap lock 阻塞。

    在学习 MySQL 过程中,一般只有在它被阻塞的时候才能观察到,所以这也是它常常被忽略的原因吧...

    GAP LOCK

    在此例中,另外一个重要的点就是 gap lock,通常情况下我们说到 gap lock 都只会联想到 REPEATABLE-READ 隔离级别利用其解决幻读。但实际上在 READ-COMMITTED 隔离级别,也会存在 gap lock ,只发生在:唯一约束检查到有唯一冲突的时候,会加 S Next-key Lock,即对记录以及与和上一条记录之间的间隙加共享锁。

    通过下面这个例子就能验证:

    这里 session1 插入数据遇到唯一冲突,虽然报错,但是对 (15,20] 加的 S Next-Key Lock 并不会马上释放,所以 session2 被阻塞。另外一种情况就是本文开始的例子,当 session2 插入遇到唯一冲突但是因为被 X Lock 阻塞,并不会立刻报错 “Duplicate key”,但是依然要等待获取 S Next-Key Lock 。

    有个困惑很久的疑问:出现唯一冲突需要加 S Next-Key Lock 是事实,但是加锁的意义是什么?还是说是通过 S Next-Key Lock 来实现的唯一约束检查,但是这样意味着在插入没有遇到唯一冲突的时候,这个锁会立刻释放,这不符合二阶段锁原则。这点希望能与大家一起讨论得到好的解释。

    如果是在 REPEATABLE-READ,除以上所说的唯一约束冲突外,gap lock 的存在是这样的:

    普通索引(非唯一索引)的S/X Lock,都带 gap 属性,会锁住记录以及前1条记录到后1条记录的左闭右开区间,比如有[4,6,8]记录,delete 6,则会锁住[4,8)整个区间。

    对于 gap lock,相信 DBA 们的心情是一样一样的,所以我的建议是:

    1. 在绝大部分的业务场景下,都可以把 MySQL 的隔离界别设置为 READ-COMMITTED;

    2. 在业务方便控制字段值唯一的情况下,尽量减少表中唯一索引的数量。

    锁冲突矩阵

    前面我们说的 GAP LOCK 其实是锁的属性,另外我们知道 InnoDB 常规锁模式有:S 和 X,即共享锁和排他锁。锁模式和锁属性是可以随意组合的,组合之后的冲突矩阵如下,这对我们分析死锁很有帮助。

MySQL锁(死锁)

死锁的概念

  死锁是指两个或者两个以上的事务在执行过程中,因争夺锁资源而造成的一种互相等待的现象。

  解决死锁问题最简单的一种方法是超时,即当两个事务互相等待时,当一个等待时间超过设置的某一阀值时,其中一个事务进行回滚,另一个等待的事务就能继续进行。在InnoDB存储引擎中,参数Innodb_lock_wait_timeout用来设置超时的时间

  超时机制虽然简单,但是其仅通过超时后对事务进行回滚的方式来处理,或者说其是根据FIFO的顺序选择回滚事务。但若超时的事务所占权重比较大,如事务更新了很多行,占用了较多的undo log ,这时采用FIFO的方式,就显得不合适了,因为回滚这个事务的时间相对于另一个事务所占的时间可能会很多。

  因此除了超时机制,当前数据库还都普遍采用wait-for graph(等待图)的方式来进行死锁检测。较之前超时的解决方案,这是一种更为主动的死锁检测方法。InnoDB存储引擎也采用这种方式。

  wait-for graph要求数据库保存以下两种信息: 锁的信息链表,事务等待链表

  在图中,事务T1指向T2 的定义为:

    事务T1 等待事务T2所占用的资源

    事务T1 最终等待T2 所占用的资源,也就是事务之间在等待相同的资源,而事务T1发生在事务T2的后面

 

 

 

 

 

 由此,可以发现wait-for graph是一种较为主动的死锁检测机制,在每个事务请求锁并发生等待时都会判断是否存在回路,若存在则有死锁,通常来说InnoDB存储引擎选择回滚undo量最小的事务。

死锁的概率

  假设当前数据库中共有 n+1 个线程执行,即当前总共有n+1个事务。并假设每个事务所做的操作相同。

  若每个事务由r+1 个操作组成,每个操作作为从R行数据中随机地操作一行数据,并占用对象的锁。

  每个事务在执行完最后一个步骤释放所占用的所有锁资源。

  最后假设 nr << R,即线程操作的数据只占所有数据的一小部分。

  在上述的模型下,事务获取一个锁需要等待的概率是多少?当事务获得一个锁,其他任何一个事务获得锁的情况为:

        (1+2+3+....+r)/(r+1) ≈ r/2

  由于每个操作为从R行数据中取一条数据, 每行数据被取到的概率为1/R ,因此,事务中每个操作需要等待的概率PW为:

          PW = nr/2R

  事务是由r个操作所组成,因此事务发生等待的概率PW(T)为:

           PW(T) = 1-(1-PW)≈ r*PW ≈  nr² / 2R

  死锁是由于产生回路,也就是事务互相等待而发生的,若死锁的长度为2 ,即两个等待节点间发生死锁,那么其概率为:

             一个事务发生死锁的概率 ≈  PW(T)² /n ≈  nr4 / 4R2

  由于大部分死锁发生的长度为2,因此上述公式基本代表了一个事务发生死锁的概率因此,任何一个事务发生死锁的概率为:

            系统中任何一个事务发生死锁的概率  ≈  n²r/ 4R2

  从上述的公式中可以发现,由于 nr<< R,因此事务发生死锁的概率是非常低的。同时,事务发生死锁的概率与以下几点因素有关:

    系统中事务的数量 (n),数量越多发生死锁的概率越大

    每个事务操作的数量(r),每个事务操作的数量越多,发生死锁的概率越大

    操作数据的集合(R),越小则发生死锁的概率越大

死锁示例

  如果程序是串行的,那么不可能发生死锁。死锁只存在与并发的情况,而数据库本身就是一个并发运行的程序,因此可能发生死锁。

时间 会话A 会话B
1 begin;  
2

mysql> select * from test.t where a =1 for update;
+---+
| a |
+---+
| 1 |
+---+
1 row in set

gegin;
3  

mysql> select * from test.t where a = 2 for update;
+---+
| a |
+---+
| 2 |
+---+
1 row in set

4

mysql> select * from test.t where a =2 for update;

 
5  

mysql> select * from test.t where a = 1 for update;
1213 - Deadlock found when trying to get lock; try restarting transaction

6 +---+
| a |
+---+
| 2 |
+---+
1 row in set
 

  会话B事务抛出1213这个错误提示,即表示事务发生了死锁,死锁的原因是会话A和B的资源在互相等待。

  会话B中的事务抛出死锁异常后,会话A中马上得到了记录为2 的这个资源,这其实是因为会话B中的事务发生了回滚,否则会话A中的事务是不可能得到资源的。

    InnoDB存储引擎并不会回滚大部分的错误异常,但是死锁除外。发现死锁后,InnoDB存储引擎会马上回滚一个事务。因此如果在应用程序中捕获了1213这个错误,其实并不需要对其进行回滚。

  此外还存在另一种死锁,即当前事务持有待插入记录的下一个记录的X锁,但是在等待队列中存在一个S锁的请求,则可能发生死锁。

  

时间 会话A 会话B
            1 begin  
            2   begin
            3 select * from t where a = 4 for update  
            4  

select * from t where a <= 4 lock in share mode;

等待

            5

insert into t value (3);

error 1213 deadlock found when trying to get lock;try restarting transaction

 
            6   事务获得锁,继续执行

  可以看到,会话A已经对记录4持有了X锁,但是会话A中插入记录3时会导致死锁的发生。

  这个问题的产生是由于会话B中请求记录4的S锁而发生等待,但之前请求的锁对于主键值记录 1、2都已经成功,若在事件点3能插入记录,那么会话B在获得记录4持有的S锁后,还需要向后获取记录3的记录,这样就显得优点不合理。因此InnoDB存储引擎在这里主动选择了死锁,而回滚的是undo log记录大的事务,这与AB-BA死锁的处理又有所不同。

以上是关于请教一个MYSQL中死锁的问题的主要内容,如果未能解决你的问题,请参考以下文章

解决一次mysql死锁问题

mysql 发生死锁问题请求帮助

oracle死锁问题排查

mysql 发生死锁问题请求帮助

mysql 开发进阶篇系列 14 锁问题(避免死锁,死锁查看分析)

mysql复制表的时候会死锁吗