• MySQL 死锁示例


    1、什么是死锁

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

    2、死锁示例

    以下示例是基于RR隔离级别的基础下进行的。

    CREATE TABLE `t` (
      `id` int(11) NOT NULL,
      `name` varchar(10) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
    
    ## insert 两条数据
    mysql> select * from t;
    +----+-------+
    | id | name  |
    +----+-------+
    |  1 | james |
    |  2 | kobe  |
    +----+-------+
    

    2.1、经典死锁场景: 加锁顺序不一致导致的死锁

    在聚集索引上加锁(行锁)顺序不一样导致产生的死锁:两事务相互竞争对方已占有的资源,导致死锁。

    序号 事务1 事务2
    0 begin; begin;
    1 select * from t where id = 1 for update; select * from t where id = 2; for update;
    此时事务1持有id=1的行锁 此时事务1持有id=2的行锁
    2 select * from t where id = 2 for update;
    此时在等待id=2的行锁释放
    3 select * from t where id = 1 for update;
    此时事务2在等待id=1的行锁,与事务1形成循环等待,所以会导致死锁。
    ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
    这是mysql返回的,此时事务2会释放id=2的锁和取消id=1的竞争

    这只是其中一种场景,比如还会遇到根据非聚集索引进行加锁时,可能会锁住多行。所以如果多个事务加锁顺序不一样导致有竞争就可能会导致死锁

    另外提醒下:Select * from xxx where id in (xx,xx,xx) for update

    在in里面的列表值mysql是会自动从小到大排序,加锁也是一条条从小到大加的锁

    2.2、插入意向锁和间隙锁发生死锁

    当对未存在的行进行锁的时候(即使条件为主键),mysql是会锁住一段范围(有gap锁)

    序号 事务1 事务2
    0 begin; begin;
    1 select * from t where id = 11 for update; select * from t where id = 22; for update;
    由于11这条记录不存在,所以行锁转变为间隙锁,锁住的范围为(2, +∞) 同理,由于22这条记录不存在,所以行锁转变为间隙锁,锁住的范围为(2, +∞)
    所以这两个事务对同一个区间加了间隙锁,由于间隙锁是允许共存的,所以可以继续执行
    2 insert t values(11, 'oven');
    此时事务1需要对id=11加插入意向锁,但由于插入意向锁与事务2中的间隙锁冲突,发生阻塞
    3 insert t values(22, 'steven');
    此时事务2需要对id=22加插入意向锁,这时MySQL检测到死锁,回滚。

    2.3 行锁与间隙锁发生死锁

    准备如下数据:

    select * from t order by id asc;
    +----+--------+
    | id | name   |
    +----+--------+
    |  1 | james  |
    |  2 | kobe   |
    |  3 | wade   |
    |  5 | davis  |
    |  6 | paul   |
    |  7 | jordan |
    | 10 | maddie |
    +----+--------+
    7 rows in set (0.00 sec)
    
    序号 事务1 事务2
    0 begin; begin;
    1 select * from t where id = 5 for update;
    事务1对id=1这条记录加上行锁
    2 select * from t where id < 20 for update;
    此时事务2加的是间隙锁,整体区间为(-∞, 20),由于事务1持有此区间中的一个行锁, 所以需要等待事务1所持有的行锁释放。此时事务2持有的真正的锁区间仅为(-∞, 5)
    3 insert t values(4, 'test');
    事务1此时再去获取id=4的行锁, 而id=4在事务2中的间隙锁范围内, 这时MySQL检测到死锁,回滚。

    总结下:
    Session2在等待Session1的id=5的锁,session2又持了1到4的锁,最后,session1在插入新行时又得等待session2,故死锁发生了。

    这种一般是在业务需求中基本不会出现,因为你锁住了id=5,却又想插入id=4的行,这就有点跳了,当然肯定也有解决的方法,那就是重理业务需求,避免这样的写法。

    此时提个问题哈:
    第三步事务1插入的记录是:insert t values(11, 'test'); 会导致死锁吗?

    3、如何解决死锁

    1、最简单的方法就是设置超时,即当两个事务互相等待时,当一个等待时间超过设置的某一阈值时,其中一个事务进行回滚,另一个等待的事务就能继续执行。

    在innodb存储引擎中,参数innodb_lock_wait_timeout用来设置超时的时间。

    2、另外一种主动的方案:采用wait-for graph的方式来进行死锁检测,如果检测到死锁,会选择回滚undo量最小的事务;

    4、如何预防死锁

    最直接的方法就是破坏产生死锁的条件:如互斥条件、循环等待等;

    比如:

    1、不同事务中的加锁顺序尽量保持统一;

    2、尽量避免大事务,占有的资源锁越多,越容易出现死锁。建议拆成小事务

    3、尽量避免间隙锁。建议将间隙所转化为行锁

  • 相关阅读:
    《linux/unix设计思想》读后感
    webserver ZooKeeper Cluster
    OS + RedHat 6.3 x64 / sshd X11 /
    nGrinder SocketTest.groovy
    OS + Centos7.6 gdm / xmanager xstart
    OS + CentOS 7 / VirtualBox 6.0 / VMware-Workstation-Full-15.1.0
    浅谈MySQL Replication(复制)基本原理
    MySQL存储引擎比较
    explain SQL语句性能检测
    看看JavaScript中void(0)的含义
  • 原文地址:https://www.cnblogs.com/yuanfy008/p/15920718.html
Copyright © 2020-2023  润新知