`
jishuaige
  • 浏览: 10420 次
  • 性别: Icon_minigender_1
  • 来自: 成都
社区版块
存档分类
最新评论

继续关于死锁

 
阅读更多
继续关于死锁的分析:上一篇分析大多只是表面上,概念上的。这篇根据项目中实际出现的死锁问题我们再来进一步分析一下。
来分析之前,先的弄清楚几个问题:
MVCC:
在MySQL的InnoDB中,实现的是基于多版本的并发控制协议(MVCC):读不加锁,读写不冲突。
在MVCC中读操作分为两种:
快照读:读取的是记录当前的版本(有可能是历史版本),不用加锁。简单的select语句属于快照读,不加锁:select * from test where ?。
当前读:读取的是最新版本,为什么喃,因为在这种情况下的所读到的记录都会加上锁,其他的记录是不会有机会去改变这个记录的。特殊的读:select * from test where ? lock in share mode;select * from table where ? for update;insert/update/delete/;都属于当前读。

MySQL/InnoDB定义的4种隔离级别:
Read Uncommited:可以读取到未提交的记录。这种级别是不会用到的。
Read Committed(RC):对读取到记录加锁(记录锁),存在幻读现象。
Repeatable Read(RR):对读取到记录加锁(记录锁),同时要对读取记录的范围加锁,新的记录在范围内的不能插入(GAP锁),不存在幻读。
Serializable:从MVCC并发控制退化为基于锁的并发控制。所有的读操作均为当前读,读加读锁 (S锁),写加写锁 (X锁)。

意向锁:事务在请求S锁和X锁前,需要先获得对应的IS、IX锁。
意向共享锁(IS):事务想要获得一个表中某几行的共享锁。
意向排它锁(IX):事务想要获得一个表中某几行的排它锁。
意向锁不会阻塞除全表查询以外的任何请求。事务A和事务B可以同时获取同几行数据的IS和IX。

行锁:
记录锁(Record Locks):仅仅锁住索引记录的一行。在单条索引记录上加锁,永远锁住的是索引,而非记录本身。

间隙锁(Gap Locks):区间锁,锁住一个索引区间:具体在索引记录之间的间隙中加锁,或者是在某一条索引记录之前或者之后加锁,并不包括索引本身。

next-key锁(Next-Key Locks):record lock+gap lock,左开右闭区间。innodb默认使用此锁来锁定记录。但是当查询的索引含有唯一属性的时候,Next-Key Lock会进行优化,将其降级为Record Lock,即仅锁住索引本身,不是范围。

插入意向锁(Insert Intention Locks):在Gap Locks中存在一种插入意向锁,这个锁是在insert时产生的。在多个事务同时写入不同数据到同一索引间隙的时候,并不需要等待其他事务完成,不会发生锁等待。
假设有一个记录索引包含键值4和7,不同的事务分别插入5和6,每个事务都会产生一个加在4-7之间的插入意向锁,获取在插入行上的排它锁,但是不会被互相锁住,因为数据行并不冲突。

自增锁(AUTO-INC Locks):一种特殊的表级锁,发生在涉及到AUTO_INCREMENT列的事务性插入操作时产生。

行锁的兼容性矩阵:



表注:横向是已经持有的锁,纵向是正在请求的锁。

一:delete语句加锁机制:
无索引下的删除动作+RR:
delete from t1 where id = 10;id上没有索引,只能进行全表扫描。所有的记录都被加上X锁,每条记录间的间隙也都被加上GAP锁。

二:insert语句加锁机制:
INSERT sets an exclusive lock on the inserted row. This lock is an index-record lock, not a next-key lock (that is, there is no gap lock) and does not prevent other sessions from inserting into the gap before the inserted row.
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. Suppose that there are index records with values of 4 and 7. Separate transactions that attempt to insert values of 5 and 6 each lock the gap between 4 and 7 with insert intention locks prior to obtaining the exclusive lock on the inserted row, but do not block each other because the rows are nonconflicting.
If a duplicate-key error occurs, a shared lock on the duplicate index record is set. This use of a shared lock can result in deadlock should there be multiple sessions trying to insert the same row if another session already has an exclusive lock.

简单的insert会在insert的行对应的索引记录上加一个排它锁,这是一个record lock,并没有gap,所以并不会阻塞其他session在gap间隙里插入记录。
在insert之前,还会加一种锁,叫insertion intention gap lock,也就是意向的gap锁,这个意向gap锁的作用就是预示着当多事务并发插入相同的gap空隙时,只要插入的记录不是gap间隙中的相同位置,则无需等待其他session就可完成,这样就使得insert操作无须加真正的gap lock。并发的事务可以对同一个gap加意向gap锁。
假设发生了一个唯一键冲突错误(duplicate-key error),那么将会在重复的索引记录上加共享锁。这个共享锁在并发的情况下是会产生死锁的,比如有两个并发的insert都对要对同一条记录加共享锁。而此时这条记录又被其他事务加上了排它锁。当这个事务提交或者回滚后。两个并发的insert操作是会发生死锁的(Ta等待Tb释放共享锁才可以往下走,Tb等待Ta释放共享锁才可以往下走)。
在insert的时候用的是LOCK_X排他锁 | LOCK_GAP间隙锁 | LOCK_INSERT_INTENTION插入意向锁去检查插入的间隙,这个模式下与LOCK_S共享锁 | LOCK_GAP间隙锁的锁模式冲突,与LOCK_X排他锁 | LOCK_GAP间隙锁的锁模式冲突。但是对于相同的间隙GAP,两个锁模式为LOCK_X排他锁 | LOCK_GAP间隙锁 | LOCK_INSERT_INTENTION插入意向锁是兼容的。

insert死锁场景分析:以下场景是参考了http://yeshaoting.cn/article/database/mysql%20insert%E9%94%81%E6%9C%BA%E5%88%B6/
1. duplicate key error引发的死锁
   这个主要发生在两个以上的事务同时进行唯一键相同的记录插入操作。



如果T1是commit,T2和T3会报唯一键冲突:ERROR 1062 (23000): Duplicate entry ‘6’ for key ‘PRIMARY’
如果T1是rollback,那么T3就会报Deadlock。
这个在线下操作后的结果和以上的情景一致。

死锁成因

1,事务T1成功插入记录,并获得索引id=6上的排他记录锁(LOCK_X | LOCK_REC_NOT_GAP)。
2,紧接着事务T2、T3也开始插入记录,请求排他插入意向锁(LOCK_X | LOCK_GAP | LOCK_INSERT_INTENTION);但由于发生重复唯一键冲突,各自请求的排他插入意向锁(LOCK_X | LOCK_GAP | LOCK_INSERT_INTENTION)转成共享记录锁(LOCK_S | LOCK_REC_NOT_GAP)。
3,T1回滚释放索引id=6上的排他记录锁(LOCK_X | LOCK_REC_NOT_GAP),T2和T3都要请求索引id=6上的排他记录锁(LOCK_X | LOCK_REC_NOT_GAP)。
由于X锁与S锁互斥,T2和T3都等待对方释放S锁。
死锁便产生了!

2,GAP与Insert Intention冲突引发的死锁
表结构:
CREATE TABLE `t` (
  `a` int(11) NOT NULL,
  `b` int(11) DEFAULT NULL,
  PRIMARY KEY (`a`),
  KEY `idx_b` (`b`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

数据:
mysql> select * from t;
+----+------+
| a  | b    |
+----+------+
|  1 |    2 |
|  2 |    3 |
|  3 |    4 |
| 11 |   22 |
+----+------+



死锁成因
1,事务T1执行查询语句,在索引b=6上加排他Next-key锁(LOCK_X | LOCK_ORDINARY),会锁住idx_b索引范围(4, 22)。
2,事务T2执行查询语句,在索引b=8上加排他Next-key锁(LOCK_X | LOCK_ORDINARY),会锁住idx_b索引范围(4, 22)。由于请求的GAP与已持有的GAP是兼容的,因此,事务T2在idx_b索引范围(4, 22)也能加锁成功。
3,事务T1执行插入语句,会先加排他Insert Intention锁。由于请求的Insert Intention锁与已有的GAP锁不兼容,则事务T1等待T2释放GAP锁。
4,事务T2执行插入语句,也会等待T1释放GAP锁。
死锁便产生了。

回到项目中产生死锁的情况:
请看下面的报错日志(MySQL的):
BACKGROUND THREAD
-----------------
srv_master_thread loops: 990 srv_active, 0 srv_shutdown, 8035 srv_idle
srv_master_thread log flush and writes: 9025
----------
SEMAPHORES
----------
OS WAIT ARRAY INFO: reservation count 3350
OS WAIT ARRAY INFO: signal count 3305
RW-shared spins 0, rounds 4136, OS waits 2044
RW-excl spins 0, rounds 561, OS waits 14
RW-sx spins 56, rounds 303, OS waits 3
Spin rounds per wait: 4136.00 RW-shared, 561.00 RW-excl, 5.41 RW-sx
------------------------
LATEST DETECTED DEADLOCK
------------------------
2017-03-21 15:10:36 0x2d08
*** (1) TRANSACTION:
TRANSACTION 3342674, ACTIVE 0 sec inserting, thread declared inside InnoDB 5000
mysql tables in use 1, locked 1
LOCK WAIT 6 lock struct(s), heap size 1136, 4 row lock(s), undo log entries 1
MySQL thread id 142, OS thread handle 2932, query id 61023 localhost 127.0.0.1 root update
insert into XXXX
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 4580 page no 3 n bits 72 index PRIMARY of table `XXX`.`XXX` trx id 3342674 lock_mode X insert intention waiting
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
 0: len 8; hex 73757072656d756d; asc supremum;;

*** (2) TRANSACTION:
TRANSACTION 3342675, ACTIVE 0 sec inserting, thread declared inside InnoDB 5000
mysql tables in use 1, locked 1
6 lock struct(s), heap size 1136, 4 row lock(s), undo log entries 1
MySQL thread id 147, OS thread handle 11528, query id 61024 localhost 127.0.0.1 root update
insert into XXX
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 4580 page no 3 n bits 72 index PRIMARY of table `XXX`.`XXX` trx id 3342675 lock_mode X
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
 0: len 8; hex 73757072656d756d; asc supremum;;

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 4580 page no 3 n bits 72 index PRIMARY of table `XXX`.`XXX` trx id 3342675 lock_mode X insert intention waiting
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
 0: len 8; hex 73757072656d756d; asc supremum;;

*** WE ROLL BACK TRANSACTION (2)
------------
TRANSACTIONS
------------
Trx id counter 3343825
Purge done for trx's n:o < 3343825 undo n:o < 0 state: running but idle
History list length 2
LIST OF TRANSACTIONS FOR EACH SESSION:
---TRANSACTION 283307779134672, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
---TRANSACTION 283307779139904, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
---TRANSACTION 283307779139032, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
---TRANSACTION 283307779137288, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
---TRANSACTION 283307779133800, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
---TRANSACTION 283307779132928, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
---TRANSACTION 283307779135544, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
---TRANSACTION 283307779132056, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
---TRANSACTION 283307779131184, not started
0 lock struct(s), heap size 1136, 0 row lock(s)

因为涉及到项目中我只是把表和insert的字段隐藏了。其他的都是原样Log。
上面涉及到2个事务,这2个事务里面分别在做delete和insert操作。delete语句删除是使用没有索引的字段在删除,并且删除一片数据;insert语句在新增的数据是在delete语句中删除的数据。
根据前面介绍的经验,delete删除的时候会加X锁和GAP锁,那么在两个事务在delete的时候,因为GAP锁是兼容的,所有两个事务会获取到同样间隙的锁。然后再执行insert的时候,会对同在一个间隙锁上申请插入意向锁,因为插入意向锁和GAP锁是不兼容的。所有事务A在等待事务B释放间隙锁,事务B在等待事务A释放间隙锁,这样就会出现上面的死锁了。
  • 大小: 15.7 KB
  • 大小: 14.4 KB
  • 大小: 12.8 KB
分享到:
评论

相关推荐

    oracle中关于死锁的处理

    ### Oracle中关于死锁的处理 #### 死锁概述 在Oracle数据库中,死锁是一种常见但必须妥善处理的问题。当两个或多个事务互相等待对方释放资源时就会发生死锁。这种情况下,没有一个事务能够继续执行,直到系统采取...

    sqlserver如何快速查询死锁语句

    在SQL Server数据库管理系统中,死锁是一个常见的性能问题,它发生在两个或多个事务相互等待对方释放资源,导致它们都无法继续执行。死锁不仅影响数据库的正常运行,还可能导致数据一致性问题。本文将详细介绍如何在...

    mysql死锁的一些案例

    死锁是指两个或多个事务在执行过程中,因争夺资源而造成的一种相互等待的现象,若无外力干涉它们将无法继续执行。死锁是数据库系统中常见的问题,尤其在多用户、高并发的应用场景下,了解和处理死锁显得尤为重要。 ...

    oracle死锁原因解决办法

    除了alert.log外,Oracle还会为每个死锁生成一个跟踪文件(trace file),该文件包含了更多关于死锁的信息。在上述示例中提到的跟踪文件路径为 `e:\oracle\admin\GEDEON\udump\ORA01784.TRC`。跟踪文件中包含的详细...

    DB2解决表死锁

    死锁是指两个或多个事务在执行过程中,因争夺资源而造成的一种相互等待的现象,若无外力干涉,它们都将无法继续执行。在DB2中,这种情况可能导致系统性能下降,甚至阻塞正常业务操作。 DB2通过一种称为“死锁检测”...

    orcale死锁查杀PB版

    Oracle数据库在运行过程中,由于多个事务间的资源竞争,可能会出现死锁现象,导致某些事务无法继续执行。"Oracle死锁查杀PB版"是专为解决这类问题而设计的工具,它提供了便捷的方法来检测和解除数据库中的死锁。本文...

    通过SQL Server Profiler来监视分析死锁

    死锁发生时,两个事务A和B各自持有对方需要的资源,导致双方都无法继续执行,形成一种僵持状态。为了解决这个问题,SQL Server引入了死锁检测机制,并会在发现死锁时选择一个事务进行回滚,以打破僵局,这个过程称为...

    oracle数据库解决死锁

    死锁是当多个事务在等待其他事务释放资源时,导致系统无法继续执行的现象。 在Oracle数据库中,死锁可能是由多种原因引起的,例如资源竞争、锁定冲突、事务延迟等。为了解决死锁问题,需要使用PL/SQL语句来检测和...

    死锁详解,如何表面死锁

    死锁是多任务操作系统中可能出现的一种异常状态,当两个或多个进程互相等待对方释放资源而无法继续执行时,系统就陷入了死锁。死锁的四个必要条件包括: 1. **互斥条件**:某些资源一次只能被一个进程使用,即在一...

    模拟死锁检测算法[死锁检测算法实现]

    死锁是指两个或两个以上的进程在竞争资源时,导致系统无法继续执行下去的状态。死锁检测算法的主要任务是通过分析系统中的进程和资源之间的关系,来确定是否出现死锁。 在本实现中,我们将使用死锁检测算法来检测...

    数据库死锁-解决死锁问题的三种办法

    死锁发生时,两个或更多的事务互相等待对方释放资源,从而导致所有事务都无法继续执行,形成僵局。针对这一问题,数据库管理系统通常采用三种策略来解决:预防死锁、检测死锁以及避免死锁。 ### 预防死锁 预防死锁...

    sql数据库死锁查询工具

    SQL死锁是指两个或多个事务在执行过程中,因争夺资源而造成的一种相互等待的现象,若无外力干涉,它们都将无法继续执行。这种情况通常发生在以下场景:事务A持有资源X并请求资源Y,同时事务B持有资源Y并请求资源X,...

    一次死锁追踪经历

    死锁是数据库管理系统中的一种常见问题,发生在两个或多个事务相互等待对方释放资源,导致它们都无法继续执行的情况。在这种情况下,如果没有外部干预,这些事务将永久阻塞,严重影响系统的性能和稳定性。在本文中,...

    浅谈死锁(Linux操作系统)

    如果检测到死锁,系统会采取措施撤销或挂起一些进程,以回收资源并将其重新分配给阻塞状态的进程,使其能够继续运行。 #### 四、Linux操作系统中的死锁处理 在Linux等现代操作系统中,处理死锁问题时通常采用以下...

    银行家算法 关于死锁

    死锁,作为并发控制中的一个严重问题,一旦发生,会导致多个进程无法继续执行,从而影响整个系统的正常运行。银行家算法,由艾兹格·迪杰斯特拉提出,是一种有效的预防死锁的策略,尤其在多任务并行环境中,其重要性...

    死锁的检测与接触 C语言

    在操作系统领域,死锁是指两个或多个并发进程互相等待对方持有的资源而无法继续执行的状态。本实验报告关注的是死锁的检测与解除,通过C语言实现。实验的主要目的是理解操作系统如何检测死锁的发生,并在发生死锁时...

    查看数据库死锁信息

    死锁指的是两个或多个事务在执行过程中,因争夺资源而造成的一种相互等待的现象,若无外力干涉它们将无法继续执行。本篇文章将详细讲解如何查看和分析MySQL数据库中的死锁信息。 标题所提及的"查看数据库死锁信息...

    ORA-00060: 等待资源时检测到死锁--oracle 数据库表死锁异常

    在Oracle数据库系统中,"ORA-00060: 等待资源时检测到死锁" 是一个常见的错误提示,它表明两个或多个事务在执行过程中陷入了无法继续进行的状态,因为彼此都在等待对方释放资源。这种情况通常发生在并发操作中,比如...

Global site tag (gtag.js) - Google Analytics