• innodb之死锁分析:读和删造成的死锁


    一个例子的死锁分析:

    环境:innodb引擎,RC隔离级别;

    死锁信息:

    RECORD LOCKS space id 0 page no 1492482 n bits 904 index `unit_id` of table `51fshenzhen`.`t_refresh_queue` trx id EB9C4A64 lock_mode X locks rec but not gap

    表如下:

    CREATE TABLE `t_refresh_queue` (
      `refresh_queue_id` int(11) NOT NULL AUTO_INCREMENT,
      `unit_type` tinyint(1) DEFAULT '0',
      `unit_id` int(11) DEFAULT '0',
      `refresh_time` int(11) DEFAULT '0',
      `broker_id` int(11) DEFAULT '0',
      `agent_id` int(11) DEFAULT '0',
      `company_id` int(11) DEFAULT '0',
      `sector_id` int(11) DEFAULT '0',
      PRIMARY KEY (`refresh_queue_id`),
      UNIQUE KEY `unit_id` (`unit_id`,`unit_type`,`refresh_time`),
      KEY `index_broker_id` (`broker_id`),
      KEY `index_refresh_time` (`refresh_time`)
    ) ENGINE=InnoDB AUTO_INCREMENT=175716299 DEFAULT CHARSET=gbk;

    删除SQL:

    DELETE FROM `t_refresh_queue` 
    WHERE `unit_id` IN ('1451914','1486249','1486175','1451826','1447935','1447807','1447624','1440273','1469855') AND `unit_type` = '1';

    插入SQL:

    INSERT INTO `t_refresh_queue` (`unit_type`, `unit_id`, `refresh_time`, `broker_id`, `agent_id`, `sector_id`, `company_id`) 
    VALUES  ('1', '1451914', '1399806000', '74146', '0', '0', '2758'), 
    ('1', '1451914', '1399811400', '74146', '0', '0', '2758'),
    ('1', '1451914', '1399816200', '74146', '0', '0', '2758'),
    ('1', '1451914', '1399818900', '74146', '0', '0', '2758'),
    ('1', '1451914', '1399823400', '74146', '0', '0', '2758'),
    ('1', '1486249', '1399806000', '74146', '0', '0', '2758'),
    ('1', '1486249', '1399811400', '74146', '0', '0', '2758'),
    ('1', '1486249', '1399816200', '74146', '0', '0', '2758'),
    ('1', '1486249', '1399818900', '74146', '0', '0', '2758'),
    ('1', '1486249', '1399823400', '74146', '0', '0', '2758'),
    ('1', '1486175', '1399806000', '74146', '0', '0', '2758'),
    ('1', '1486175', '1399811400', '74146', '0', '0', '2758'),
    ('1', '1486175', '1399816200', '74146', '0', '0', '2758');

    首先、需要明确下面几个内容:

    (1)RC禁止了gap锁

    (2)in子句是索引最左前缀,因此delete不走索引;

    SQL执行过程分析

    1、删除操作的sql;

    DELETE FROM `t_refresh_queue` WHERE `unit_id` IN ('1451914','1486249','1486175','1451826','1447935','1447807','1447624','1440273','1469855') and `unit_type` = '1';

    index:unit_id子句IN,不走索引,只能进行全表扫描;

    删除执行过程如下:首先进行全表扫描,便利每一条记录,匹配unit_id,unit_type,并对所有便利过的行加X锁;

    锁 refresh_queue_id unit_id  unit_type
    X  rqid1            1447935  1
    X  rqid2            1486175  1
    X  rqid4            1486249  1
    X  rqid7            1440273  1
    X  rqid8            1451914  1
    X  rqid9            1469855  1
    X  rqid23           1447807  1
    X  rqid34           1451826   1
    X  rqid45           1447624  1

    在具体以[unit_id,unit_type]为(1451914,1)的行为例来说明:

    具体sql变为:DELETE FROM `t_refresh_queue` WHERE `unit_id` IN ('1451914') AND `unit_type` = '1';为了保留不走索引的特点,还采用in。

    锁 refresh_queue_id unit_id unit_type
    X ...
    X rqid80        1451913 1
    X rqid81        1451914 1
    X ...
    X rqid83        1451914 1
    X ...
    X rqid88        1451914 1
    X rqid89        1451914 1
    X rqid90        1451915 1
    X ...

    再对这四条数据进行删除操作,保持行级X锁,获取页级X锁,标记对应页为dirty,等待之后的purge的时候进行物理删除,释放行级X锁,释放页级X锁;

    2、对于插入操作

    执行插入操作的时候,走索引unit_id,并且是完全覆盖唯一索引。继续来以unit_id=1451914来分析,对于下面五条数据,插入过程:

    ('1', '1451914', '1399806000', '74146', '0', '0', '2758')          键值:[1451914,1,1399806000]
    ('1', '1451914', '1399811400', '74146', '0', '0', '2758')                键值:[1451914,1,1399811400]
    ('1', '1451914', '1399816200', '74146', '0', '0', '2758')                键值:[1451914,1,1399816200]
    ('1', '1451914', '1399818900', '74146', '0', '0', '2758')                键值:[1451914,1,1399818900]
    ('1', '1451914', '1399823400', '74146', '0', '0', '2758')                键值:[1451914,1,1399823400]

    首先在索引unit_id中查找对应键值,顺序为[unit_id,unit_type,refresh_time]

    其中Index key:unit_id,unit_type,refresh_time  决定了插入范围 [1451914,1,1399805999]~[1451914,1,1399806001]

      Index filer:is null

      TABLE FILE:broker_id,agent_id,sector_id,company_id

    首先查看是否存在[1451914,1],存在,则查找[1451914,1,1399805999]~[1451914,1,1399806001]中是否存在[1451914,1,1399806000],并且在[1451914,1,1399805999]和[1451914,1,1399806001]索引键值上添加S锁。结果如下:

    锁 unit_id unit_type refresh_time refresh_queue_id
    S  1451914 1      1399805999    rqid8100001
    ...
    S  1451914 1      1399806001    rqid8100010

    索引中有[1451914,1,1399806000]:二级索引不用发生改变,在[1451914,1,1399806000]上加上S锁,并返回refresh_queue_id id值,并且根据TABLE FILE信息来匹配;所有便利过的行添加X锁,对该位置的数据页添加X锁,写入数据,释放数据页的X锁,对数据行添加X锁,进行下一行;

    索引中没有[1451914,1,1399806000]:二级索引需要发生改变。获取数据页X锁,写入行数据,主键ID自增,释放数据页X锁,添加主键X锁,修改二级索引,加入[1451914,1,1399806000]值其他二级索引值该挪的挪位置。

    知道这个过程后,我们来看如何发生的死锁:

  • 相关阅读:
    iOS 自动化测试踩坑(二):Appium 架构原理、环境命令、定位方式
    干货 | 掌握 Selenium 元素定位,解决 Web 自动化测试痛点
    代理技术哪家强?接口 Mock 测试首选 Charles
    浅谈MVC缓存
    PetaPoco 快速上手
    解释器模式(26)
    享元模式(25)
    中介者模式(24)
    职责链模式(23)
    命令模式(22)
  • 原文地址:https://www.cnblogs.com/wyett/p/mysql_innodb_locks_analysis1.html
Copyright © 2020-2023  润新知