InnoDB学习之死锁[通俗易懂]

InnoDB学习之死锁[通俗易懂]InooDB的死锁例子和死锁检测机制。

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全家桶1年46,售后保障稳定

死锁

概念

​ 死锁是指由于每个事务都持有对方需要的锁而无法进行其他事务的情况,形成一个循环的依赖关系。因为这两个事务都在等待资源变得可用,所以两个都不会释放它持有的锁。

会话A持有一行数据的锁,会话B持有另一行数据的锁。
A申请获取B持有的那个锁,但是被B占用着,所以A等待。
B申请获取A持有的那个锁,但是被A占用着,所以B等待。

InnoDB死锁示例

  1. 以下示例说明了锁定请求将导致死锁时如何发生错误。该示例涉及两个客户端A和B。

    首先,客户端A创建一个包含一行的表,然后开始事务。在事务中,A通过LOCK IN SHARE MODE来获得对行的读锁:

    mysql> CREATE TABLE t (i INT) ENGINE = InnoDB;
    Query OK, 0 rows affected (1.07 sec)
    
    mysql> INSERT INTO t (i) VALUES(1);
    Query OK, 1 row affected (0.09 sec)
    
    mysql> START TRANSACTION;
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> SELECT * FROM t WHERE i = 1 LOCK IN SHARE MODE;
    +------+
    | i    |
    +------+
    | 1    |
    +------+
    

    Jetbrains全家桶1年46,售后保障稳定

    接下来,客户端B开始事务并尝试从表中删除该行:

    mysql> START TRANSACTION;
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> DELETE FROM t WHERE i = 1;
    

    删除操作需要一个X锁。无法授予该S锁,因为它与客户端A持有的锁不兼容 ,因此该请求进入针对行和客户端B块的锁请求队列中。

    最后,客户端A还尝试从表中删除该行:

    mysql> DELETE FROM t WHERE i = 1;
    ERROR 1213 (40001): Deadlock found when trying to get lock;
    try restarting transaction
    

    此处发生死锁,因为客户端A需要 X锁才能删除该行。但是,不能授予该锁定请求,因为客户端B已经有一个X锁定请求,并且正在等待客户端A释放其S锁定。由于B事先要求锁,因此SA持有的锁也不能 升级 XX锁。结果, InnoDB为其中一个客户端生成错误并释放其锁。客户端返回此错误。

    届时,可以授予对另一个客户端的锁定请求,并从表中删除该行。

  2. 在RR隔离级别下,数据库有两条数据id=1和id=10。 且id为普通索引
    两个事务,事务 1: select * from table where id=3 for update。 insert (id=3),他会在(1,3) (3,10)之间加间隙锁,在(3)这个位置申请插入意向锁。
    事务2: select * from table where id=5 for update, insert(id=5)。 它会在(1,5) (5,10)之间加间隙锁,由于间隙锁互相兼容,故该锁可以获取,另外在(5)这个地方申请插入意向锁。
    当事务1要获取插入意向锁时,发现(3)被事务2的间隙锁锁住了,故等待事务2释放锁;
    事务2要获取插入意向锁时,发现(5)被事务1的间隙锁锁住了,故等待事务1释放锁;——死锁形成

  3. 银行转账的例子

    create table money(id int primary key,price int);
    insert into money values(1,1000);
    insert into money values(2,1000);
    

    事务A: 更新表,id=1的记录

    mysql> start transaction;
    Query OK, 0 rows affected (0.01 sec)
     
    mysql> update money set price=2000 where id=1;
    Query OK, 1 row affected (0.03 sec)
    

    事务B: 更新表,id=2的记录

    mysql> start transaction;
    Query OK, 0 rows affected (0.01 sec)
     
    mysql> update money set price=2000 where id=2;
    Query OK, 1 row affected (0.00 sec)
    

    事务A: 更新表,id=2的记录,此时会卡住(因为这条记录被加上了X锁)

    mysql> update money set price=3000 where id=2;
    

    事务B: 更新表,id=1的记录,此时会报错事务进行回滚,并且事务1会执行更新id=2的记录

    mysql> update money set price=3000 where id=1;
    ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
    

    上述,事务抛出1213这个出错提示,即发生了死锁,上例中当两个事务都执行了第一条UPDATE语句,更新了一行数据,同时也锁定了该行数据,接着每个事务都尝试去执行第二条UPDATE语句,却发现该行已经被对方锁定,然后两个事务都等待对方释放锁,同时又持有对方需要的锁,则陷入死循环。除非有外部因素接入才可能解除死锁。为了解决这种问题,数据库系统实现了各种死锁检测和死锁超时机制
    在这里插入图片描述

死锁的必要条件

  1. 多个并发事务(2个或者以上);
  2. 每个事务都持有锁(或者是已经在等待锁);
  3. 每个事务都需要再继续持有锁(为了完成事务逻辑,还必须更新更多的行);
  4. 事务之间产生加锁的循环等待,形成死锁。

总结:当两个或多个事务相互持有对方需要的锁时,就会产生死锁。

死锁的检测

当死锁检测启用时(默认),InnoDB会自动检测事务死锁并回滚一个或多个事务来打破死锁。InnoDB尝试选择小事务进行回滚,其中事务的大小由插入、更新或删除的行数决定。

InnoDB在innodb_table_locks = 1(默认值)和autocommit = 0时知道表锁,它上面的MySQL层知道行锁。否则,InnoDB无法检测到由MySQL锁表语句设置的表锁,或由InnoDB以外的存储引擎设置的锁。通过设置innodb_lock_wait_timeout系统变量的值来解决这些情况。

如果InnoDB监视器输出的最新检测到的死锁部分包含一条消息,“在锁表等待图中搜索太深或太长,我们将在事务之后回滚”,这表明等待列表中的事务数量已经达到了200的上限。超过200个事务的等待列表被视为死锁,试图检查等待列表的事务被回滚。如果锁定线程必须查看等待列表中事务拥有的超过1,000,000个锁,也可能会发生同样的错误。

死锁检测机制 – wait-for graph

核心就是数据库会把事务单元锁维持的锁和它所等待的锁都记录下来,Innodb提供了waitfor graph算法来主动进行死锁检测,每当加锁请求无法立即满足需要进入等待时,waitfor graph算法都会被触发。当数据库检测到两个事务不同方向地给同一个资源加锁(产生循序),它就认为发生了死锁,触发waitfor graph算法。比如,事务1给A加锁,事务2给B加锁,同时事务1给B加锁(等待),事务2给A加锁就发生了死锁。那么死锁解决办法就是终止一边事务的执行即可,这种效率一般来说是最高的,也是主流数据库采用的办法。

在这里插入图片描述

Innodb目前处理死锁的方法就是将持有最少行级排他锁的事务进行回滚。这也是相对比较简单的死锁回滚方式。死锁发生以后,只有部分或者完全回滚其中一个事务,才能打破死锁。对于事务型的系统,这是无法避免的,所以应用程序在设计必须考虑如何处理死锁。大多数情况下只需要重新执行因死锁回滚的事务即可。

waitfor graph原理

我们怎么知道图中四辆车是死锁的?

在这里插入图片描述

他们相互等待对方的资源,而且形成环路!我们将每辆车看为一个节点,当节点1需要等待节点2的资源时,就生成一条有向边指向节点2,最后形成一个有向图。我们只要检测这个有向图是否出现环路即可,出现环路就是死锁!这就是waitfor graph算法。
在这里插入图片描述

Innodb将各个事务看为一个个节点,资源就是各个事务占用的锁,当事务1需要等待事务2的锁时,就生成一条有向边从1指向2,最后行成一个有向图。

禁用死锁检测

在高并发性系统中,当多个线程等待同一锁时,死锁检测可能导致速度下降。有时,禁用死锁检测并依赖于innodb_lock_wait_timeout设置在发生死锁时执行事务回滚可能更有效。可以使用innodb_deadlock_detect配置选项禁用死锁检测。

避免死锁

死锁是事务性数据库中的一个典型问题,但它们并不危险,除非它们非常频繁以至于您根本无法运行某些事务。通常,您必须编写应用程序,以便在事务因死锁而回滚时,它们始终准备重新发出事务。

InnoDB使用自动行级锁定。即使在只插入或删除单行的事务中,也会出现死锁。这是因为这些操作并不是真正的“原子”操作;它们自动设置插入或删除行的索引记录(可能有几个)的锁。

你可以使用以下技巧来处理死锁,并降低发生死锁的可能性:

  • 在任何时候,发出显示引擎INNODB状态命令来确定最近死锁的原因。这可以帮助您优化应用程序以避免死锁。

  • 如果经常出现死锁警告,那么可以通过启用innodb_print_all_deadlocks配置选项来收集更多的调试信息。关于每个死锁的信息,而不仅仅是最近的死锁,都记录在MySQL错误日志中。完成调试后禁用此选项。

  • 如果事务由于死锁而失败,请随时准备重新发出事务。死锁并不危险。再试一次。

  • 保持事务较小且持续时间较短,以减少冲突的发生。

  • 在进行一组相关更改之后立即提交事务,以减少冲突的发生。特别是,不要让一个交互式mysql会话长时间打开一个未提交的事务。

  • 如果你使用锁读(SELECT … FOR UPDATE 或 SELECT … LOCK IN SHARE MODE),尝试使用较低的隔离级别,如READ COMMITTED。

  • 当修改一个事务中的多个表或同一表中的不同行集时,每次都要按照一致的顺序执行这些操作。这样,事务就形成了定义良好的队列,不会死锁。例如,将数据库操作组织成应用程序中的函数,或调用存储过程,而不是在不同的地方编写多个类似的INSERT、UPDATE和DELETE语句序列。

  • 向表中添加精心选择的索引。这样,查询需要扫描的索引记录就更少,因此设置的锁就更少。使用EXPLAIN SELECT来确定MySQL服务器认为哪些索引最适合您的查询。

  • 使用更少的锁定。如果允许SELECT从旧快照返回数据,则不要向其添加用于更新或锁定共享模式的子句。这里使用READ COMMITTED隔离级别很好,因为同一事务中的每次一致读取都是从它自己的新快照中读取的。

  • 如果没有其他帮助,使用表级锁序列化事务。对于事务性表,比如InnoDB表,使用锁表的正确方法是在事务开始时设置autocommit = 0(不是启动事务),然后是锁表,并且在显式提交事务之前不调用解锁表。例如,如果您需要写入表t1和读取表t2,您可以这样做:

    SET autocommit=0;
    LOCK TABLES t1 WRITE, t2 READ, ...;
    ... do something with tables t1 and t2 here ...
    COMMIT;
    UNLOCK TABLES;
    

    表级锁可以防止对表的并发更新,从而避免死锁,但对于繁忙的系统,响应能力会降低。

  • 序列化事务的另一种方法是创建一个只包含一行的辅助“信号量”表。让每个事务在访问其他表之前更新该行。这样,所有的事务都以连续的方式发生。注意,InnoDB的即时死锁检测算法也适用于这种情况,因为序列化锁是行级锁。对于MySQL表级锁,必须使用超时方法来解决死锁。

  • 优化表结构,优化schema,可在一定程度上避免死锁

  • 给记录集显式加锁时,最好一次性请求足够级别的锁。比如要修改数据的话,最好直接申请排他锁,而不是先申请共享锁,修改时再请求排他锁,这样容易产生死锁;

  • 尽量用相等条件访问数据,这样可以避免间隙锁对并发插入的影响; 不要申请超过实际需要的锁级别;除非必须,查询时不要显示加锁;

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/234426.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • Java优先级队列PriorityQueue「建议收藏」

    Java优先级队列PriorityQueue「建议收藏」目录普通队列对比优先级队列:逆序优先级队列自定义优先级队列的优先级相较于普通先进先出队列来说,优先级队列会根据优先级进行由高到低排序,出队时优先级高的先出队。普通队列对比优先级队列:1.普通队列:importjava.util.LinkedList;importjava.util.Queue;publicclassMainTest{publicstaticvoidmain(String[]args){ Queue<Integer>queue

  • Cortex-A53架构(记笔记的方法)

    1.前言一颗芯片最主要的就是CPU核了,处理CPUCore之外,还存在很多其他IP,包括Graphical、Multimedia、MemoryController、USBController等等。ARMproducts列出了主要产品,其中Architecture和Processors需要重点关注。Architecture扩展的四大领域:SecurityExtensio…

  • php建站错误代码0xc0000005,0xc0000005是什么错误-0xc0000005错误代码解决方法介绍-沧浪系统…

    php建站错误代码0xc0000005,0xc0000005是什么错误-0xc0000005错误代码解决方法介绍-沧浪系统…我们的电脑在进行各种各样的操作的时候有时会出现一些我们看不懂的报错,这种不知道错在哪了的问题让人很烦恼,下面就让小编来给大家介绍一下0xc0000005错误代码解决方法介绍吧。0xc0000005错误代码介绍0xc0000005这个错误一般是以显卡驱动模块相关,或者是第三方软件而引起的系统错误。解决方法一、如果是问题出在系统模块,那就需要厂商网站下载适用系统的最新驱动。二、如果是第三方软件引起的就…

  • intel处理器历代产品_英特尔酷睿历代提升

    intel处理器历代产品_英特尔酷睿历代提升悉数历史英特尔历代经典CPU产品回顾从英特尔于1971年推出首款4004微处理器到现在,英特尔处理器已经走过了40个年头。在告别13年传奇品牌奔腾之后,我们又迎来新一代酷睿i双核处理器。现在,我们就来回顾一下英特尔处理器40年来的发展历程。1971年:4004微处理器4004是英特尔推出的第一款微处理器。这一突破性的发明最先应用于Busicom

  • ssm和c3p0连接池配置文件的详解

    ssm和c3p0连接池配置文件的详解spring.xml配置&lt;?xmlversion="1.0"encoding="UTF-8"?&gt;&lt;beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:co…

  • pb数据库连接_jdbc连接mysql中文乱码

    pb数据库连接_jdbc连接mysql中文乱码最近需要用pb联mysql做个项目,查网上有关的方法,很多都没说清楚,所以在这里总结下:  采用JDBC连接,首先去MYSQL官网下载mysql-connector-java-5.0.7.rar JDBC驱动打开PB,菜单Tools–>systemoptions,打开JAVA选项,点击新增文件(白色文件图标)选择刚解压的mysql-connector-java

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号