首页 > 数据库 >科普文:软件架构数据库系列之【MySQL死锁案例分析:间隙锁“Gap Lock”导致的死锁及解决方案 ERROR 1213 (40001): Deadlock】

科普文:软件架构数据库系列之【MySQL死锁案例分析:间隙锁“Gap Lock”导致的死锁及解决方案 ERROR 1213 (40001): Deadlock】

时间:2024-10-19 20:17:41浏览次数:7  
标签:1213 lock gap 死锁 Lock CSDN 软件架构 MySQL

概叙

科普文:软件架构数据库系列之【详解MySQL死锁】-CSDN博客

科普文:软件架构数据库系列之【MySQL死锁案例分析: index_merge导致的死锁及解决方案 ERROR 1213 (40001): Deadlock】-CSDN博客

科普文:软件架构数据库系列之【MySQL死锁案例分析:加锁顺序“循环等待”导致的死锁及解决方案 ERROR 1213 (40001): Deadlock】-CSDN博客

Record Lock行锁的循环等待导致的死锁 

科普文:软件架构数据库系列之【MySQL死锁案例分析:加锁顺序“循环等待”导致的死锁及解决方案 ERROR 1213 (40001): Deadlock】-CSDN博客

Innodb锁相关知识梳理

InnoDB有三种行锁的算法:

1.Record Lock:是加在索引记录上的。

2.Gap Lock(间隙锁):对索引记录间的范围加锁,或者加在最后一个索引记录的前面或者后面

3.Next-Key Lock:前两种锁的结合,锁定一个范围,并且锁定记录本身,主要目的是解决幻读的问题。

间隙锁主要是防止幻象读,用在Repeated-Read(简称RR)隔离级别下。在Read-Commited(简称RC)下,一般没有间隙锁(有外键情况下例外,此处不考虑)。间隙锁还用于statement based replication

间隙锁有些副作用,如果要关闭,一是将会话隔离级别改到RC下,或者开启 innodb_locks_unsafe_for_binlog(默认是OFF)。

间隙锁(无论是S还是X)只会阻塞insert操作。

关于MySQL innodb的锁和加锁顺序可参考:

科普文:软件架构数据库系列之【MySQL 是怎么做并发控制的?】马国庆(翊云)-CSDN博客

科普文:软件架构数据库系列之【MySQL 执行DQL/DML/DDL必加的自动表级锁:MDL元数据锁Meta Data Lock】-CSDN博客

实战:MySQL之Innodb中的锁_innodb 锁-CSDN博客 

科普文:软件架构数据库系列之【MySQL 执行DQL/DML/DDL必加的自动表级锁:MDL元数据锁Meta Data Lock】-CSDN博客

科普文:软件架构数据库系列之【Innodb的锁和MVCC】-CSDN博客

科普文:软件架构数据库系列之【MySQL8.0新特性-并行DDl-innodb_ddl_threads】_mysql 并行查询-CSDN博客

科普文:软件架构数据库系列之【MySQL锁梳理】_一个sql语句相当于一个事务、-CSDN博客

Gap Lock间隙锁的“delete/update空行”导致的死锁 

 ok在说结果前,我们先看一下MySQL Bugs: #1866: Attempting to insert into a gap you hold a row lock on can deadlock

这个“bug” ,从标题就可以看到insert插入时gap lock导致deadlock。

详细bug描述

[17 Nov 2003 20:22] Will Bryant
Description:
While using InnoDB tables and the repeatable read isolation level, the following sequence of events:

1. One connection does a non-insert update operation (UPDATE, SELECT ... FOR UPDATE, or DELETE) but there is no matching row, giving this connection a lock on that gap
2. A second connection does a non-insert update operation on the same gap (blocks, waiting for the gap lock)
3. The first connection then tries to do an INSERT into that gap

is incorrectly detected as a deadlock (and the first connection's transaction is rolled back).

In other words, if one connection attempts to insert into a gap that it already holds a lock on, a deadlock condition is encountered if another connection has started waiting on that gap lock in the meantime (eg. by attempting to update that index position).

This is not correct behaviour; the connection already holds the lock on that index position, so it doesn't need to re-acquire it and so there is in fact no deadlock.

How to repeat:
To set up, run:
CREATE TABLE test (pri_id INT NOT NULL PRIMARY KEY, other_data INT NOT NULL) TYPE=InnoDB;

Use two connections, one for the statements on the left, the other for the statements on the right, and use "SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; SET AUTOCOMMIT=0;" on both.

UPDATE test 
   SET other_data = 42
 WHERE (pri_id = 1);
--- should indicate that 0 rows were updated
--- we now have the lock on the gap

                                          UPDATE test
                                             SET other_data = 23
                                           WHERE (pri_id = 1);
                                          --- blocks, as it should, 
                                          --- waiting for the lock

INSERT INTO test
   SET pri_id = 1,
       other_data = 42;
--- the deadlock detector detects a
--- deadlock and rolls back the transaction

                                          --- this statement then executes
                                          --- (and updates 0 rows)

Interestingly it is necessary for the first statement on both connections to be something other than an INSERT (SELECT ... FOR UPDATE does the same thing, but everything works fine if you try and do INSERTs).  So it is specifically to do with the locking of _gaps_ for update.

Suggested fix:
The first transaction should continue successfully, the second transaction should continue blocking until the first commits or rolls back, and the second should then continue (in this example, updating one row if the first committed, or zero rows if it rolled back).

I'm not sure whether this is a problem with the deadlock detector or with the locking logic; but either way, this is not a real deadlock condition - the first connection already holds the lock, so it doesn't need to re-acquire it and so there is in fact no deadlock.

In more general terms, while waiting on a lock should be FIFO, if you already hold the lock in question you certainly shouldn't wait at all.

Further evidence for this argument is that, as above, if you do an INSERT and then an UPDATE on that inserted row, it won't deadlock - the second connection keeps waiting correctly - so it is a problem specifically with it being a gap lock.

大概意思就是:innodb表在RR隔离级别下,update空行后,插入记录会导致死锁。然后“Will Bryant”这哥们儿给MySQL提了bug(17 Nov 2003 20:22   MySQL Server: InnoDB storage engine  ),建议修复“In more general terms, while waiting on a lock should be FIFO, if you already hold the lock in question you certainly shouldn't wait at all.”第一个事务应该成功执行、第二个事务继续等待即可。

可惜啊,这个情况到现在的MySQL8.4  还存在。官方还把这个状态制成“Not a Bug”。

可以看到在RC下是没有这个问题的

死锁案例:Gap Lock间隙锁的“delete/update空行”导致的死锁 

在MySQL 8中可以使用SET [SESSION|GLOBAL] TRANSACTION ISOLATION LEVEL命令动态地修改事务的隔离级别。我们把全局和当前会话的隔离级别都改成RR,再复现上面gap导致死锁的案例。
SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;

死锁错误信息 :1213、1205

死锁监控信息分析

可以看到update语句都会加锁,导致后面的insert都会有锁等待lock_mode X insert intention waiting。

在InnoDB存储引擎中,RR(REPEATABLE READ)隔离级别下,当一个事务T1正在读取一条记录,并且已经获取了共享锁(lock_mode X),同时另一个事务T2试图插入一个新记录,此时T2会进入插入意图锁(insert intention)等待状态。这是因为插入操作需要在可能冲突的范围内获取排他锁,而为了保持事务的RR隔离性,需要等待T1释放其共享锁。

死锁解决方法:

  1. 优化事务设计:确保长时间只读事务尽快完成,减少事务运行时间,避免阻塞插入操作。

  2. 检查死锁:如果系统中存在死锁,可以通过查看InnoDB的状态信息来识别并解决。

  3. 调整隔离级别:如果业务允许,可以考虑将隔离级别调低,例如使用RC(READ COMMITTED)隔离级别,可以减少锁等待,但需要注意可能引发的数据并发问题。

  4. 优化索引:确保插入操作能够利用索引,减少锁的范围。

  5. 使用可重复读事务模式时,可以通过在查询时使用SELECT ... FOR UPDATESELECT ... LOCK IN SHARE MODE来显式指定加锁方式,有针对性地加强一致性读取。

标签:1213,lock,gap,死锁,Lock,CSDN,软件架构,MySQL
From: https://blog.csdn.net/Rookie_CEO/article/details/143081467

相关文章

  • 一篇文章弄懂Redission可重入、重试锁以及MultiLock原理
    Redisson的可重入锁(ReentrantLock)是基于Redis实现的分布式锁,用于在分布式系统中提供线程安全的锁机制。它允许同一个线程在不释放锁的情况下多次获得锁,并在所有锁操作完成后,锁才真正被释放。下面我们来详细解析Redisson可重入锁的原理。基本原理可重入锁的核心思想是,同一线......
  • java_day18_多线程、线程安全问题、死锁、等待唤醒机制
    一、线程1、多线程进程:是系统进行资源分配和调用的独立单位,每一个进程都有它自己的内存空间和系统资源。举例:IDEA,阿里云盘,wegame,steam线程:是进程中的单个顺序控制流,是一条执行路径一个进程如果只有一条执行路径,则称为单线程程序。一个进程如果有多条执行......
  • 多线程(五):死锁&内存可见性问题
    目录1.synchronized---监视器锁monitorlock2.死锁2.1死锁---情况12.1.1可重入2.1.2 如何实现一个可重入锁[面试题]2.2死锁---情况22.2.1BLOCKED2.2.2手写死锁代码[经典面试题]2.3 死锁---情况33.避免死锁的出现3.1构成死锁的四个必要条件★......
  • 并发编程中锁Synchronized和ReentrantLock,CAS,AQS理解
    SynchronizedJAVA关键字,独占式的悲观锁,可重入锁。主要解决多个线程之间的访问资源的同步性,可以保证被他修饰的方法或者代码块在任意时刻只能有一个线程执行早期是重量级锁,JAVA6后引入大量优化,自旋锁,适应性自旋锁,偏向锁,轻量级锁,锁消除,锁粗化减少锁的开销使用方式修饰......
  • Android15音频进阶之4种调试线程死锁利器(八十九)
    简介:CSDN博客专家、《Android系统多媒体进阶实战》一书作者新书发布:《Android系统多媒体进阶实战》......
  • 深入理解Java并发读写锁—ReentrantReadWriteLock
    ReentrantReadWriteLock使用场景ReentrantReadWriteLock是Java的一种读写锁,它允许多个读线程同时访问,但只允许一个写线程访问(会阻塞所有的读写线程)。这种锁的设计可以提高性能,特别是在读操作的数量远远超过写操作的情况下。在并发场景中,为了解决线程安全问题,我们通常会......
  • MySQL 通过 Next-Key Locking 技术(行锁+间隙锁)避免幻读问题
    在MySQL中,InnoDB引擎通过Next-KeyLocking技术来解决幻读问题。幻读是一种事务并发问题,通常出现在RepeatableRead隔离级别下的范围查询操作中。幻读的现象是,事务在查询时多次执行相同的范围查询,但由于其他事务的插入或删除操作导致结果不一致,出现“幻觉”一样的记录。Next-KeyL......
  • 深入理解Java并发读写锁——ReentrantReadWriteLock
    ReentrantReadWriteLock使用场景ReentrantReadWriteLock是Java的一种读写锁,它允许多个读线程同时访问,但只允许一个写线程访问(会阻塞所有的读写线程)。这种锁的设计可以提高性能,特别是在读操作的数量远远超过写操作的情况下。在并发场景中,为了解决线程安全问题,我们通常会使用关......
  • 【JavaEE】【多线程】synchronized和死锁
    目录一、synchronized详解1.1互斥1.2可重入二、死锁2.1死锁成因2.2避免死锁一、synchronized详解1.1互斥synchronized会起到互斥效果,某个线程执行到某个对象的synchronized中时,其他线程如果也执行到同一个对象synchronized就会阻塞等待.语法:sy......
  • [WPF] 可选择的TextBlock,正则匹配高亮显示
    实现TextBlock控件像TextBox一样支持选择文本,又保留TextBlock显示省略号等特性,找不到参考文献了,抱歉。下面演示基于可选择TextBlock实现的效果: 代码已上传至https://gitee.com/Pumbaa_Peng/textbook-demo.git。1<pp:ChromeWindowx:Class="TextBlockDemo.MainWind......