• innodb不同场景下加锁分析总结


    Read-Uncommitted/Read-Committed隔离级别上锁过程

    查询条件

    SQL示例

    加锁分析

    查询条件为主键等值

    SELECT … WHERE PK = XX FOR UPDATE;

    1:如果有对应记录则对记录加LOCK_X|LOCK_REC_NOT_GAP锁

    2:如果没有则不加锁,以下场景都相同处理

    UPDATE … WHERE PK = XX;

    1:如果未更新其他索引列。

    只需要在主键记录上加X锁即可,加锁为LOCK_X|LOCK_REC_NOT_GAP。

    2:更新包括其他索引列。

    除了主键记录加X锁,还需要在涉及索引上加LOCK_X|LOCK_REC_NOT_GAP锁。

    DELETE … WHERE PK = XX;

    对主键、各个索引对应的记录都要加X锁,LOCK_X|LOCK_REC_NOT_GAP。

    查询条件为主键范围

    select * from t where c1 >= 20 for update;

    会对所有满足条件的记录加锁,如果没有则不加锁

    update t set c2 = c2 + 1 where c1 >= 20;

    会依次对c1 >=20的主键记录行加X锁,对应的索引行做加X锁操作。

    查询条件为唯一索引等值

    SELECT … WHERE UK = XX FOR UPDATE;

    需要在索引记录上加X锁:LOCK_X|LOCK_REC_NOT_GAP,同时还要在对应主键行上加X锁。

    UPDATE … WHERE UK = XX;

    1:未更新其他索引列

    对唯一索引上数据加X锁(LOCK_X|LOCK_REC_NOT_GAP),然后对应的主键行也需要加X锁。

    2:更新其他索引列

    依次对唯一索引数据、主键行、索引数据加X锁。

    DELETE … WHERE UK = XX;

    会对唯一索引数据加X锁,根据唯一索引找到主键行后,会再依次对主键行、唯一索引、索引数据加X锁。

    查询条件为唯一索引范围

    SELECT … WHERE UK >= XX FOR UPDATE;

    1: 如果是通过主键,则对主键行依次加X锁, 同时在加锁后,分析发现不满足条件会立即释放该行上的X锁。

    2:如果是通过唯一索引,则依次对满足条件的唯一索引、主键记录加X锁。

    UPDATE … WHERE UK <= XX FOR UPDATE;

    会对唯一索引和对应主键行依次加X锁,然后判断不满足range条件,释放唯一索引和对应主键行上的X锁。

    查询条件为非唯一索引

    SELECT … WHERE IDX = XX FOR UPDATE;

    非唯一索引情况与唯一索引情况加锁情况一致

    查询条件上无索引

    SELECT … WHERE COL = XX FOR UPDATE;

    路径选择主键,会依次对主键行加锁,分析条件,不满足条件释放锁,满足条件持有锁。

    多条件查询

    select * from t where c2 = xx and c3 = xx for update;

    当存在多个条件的时候,除了主键行上的锁,其他的加锁情况取决于选择的路径。

    选择了走唯一索引,就会对满足条件的唯一索引行加X锁,然后对主键行加X锁。

    主键路径,就会对所有行一次加X锁,分析条件,最终持有主键上c1 = 20的X锁。

    Repeatable Read隔离级别上锁过程 

    查询条件

    SQL示例

    加锁分析

    查询条件为主键等值

    SELECT … WHERE PK = XX FOR UPDATE;

    1:如果有对应记录则对记录加LOCK_X|LOCK_REC_NOT_GAP锁

    2:如果没有则会对后面的记录加GAP锁,(LOCK_X|LOCK_GAP),防止有其他语句插入pk=xx的行。

    UPDATE … WHERE PK = XX;

    1:如果未更新其他索引列。

    只需要在主键记录上加X锁即可,加锁为LOCK_X|LOCK_REC_NOT_GAP。

    2:更新包括其他索引列。

    除了主键记录加X锁,还需要在涉及索引上加LOCK_X|LOCK_REC_NOT_GAP锁。

    3:没有满足条件的记录则不加锁

    DELETE … WHERE PK = XX;

    1:如果有满足条件的记录对主键、各个索引对应的记录都要加X锁,LOCK_X|LOCK_REC_NOT_GAP。

    2:没有则不加锁

    查询条件为主键范围

    select * from t where c1 >= 20 for update;

    这里会对c1=20加X锁(LOCK_X|LOCK_REC_NOT_GAP),对c1>20对应的行加exclusive next-key lock(LOCK_X|LOCK_ORDINARY),同时会对表示记录上界的’supremum’加exclusive next-key lock。以阻塞其他事务对c1>=20的插入。

    select * from t where c1 <= 20 for update;

    这里会依次对c1<=20的主键记录加exclusive next-key lock(LOCK_X|LOCK_ORDINARY)锁。且在判断c1=30不符合查询条件后,虽然server层调用unlock_row,但对于RC隔离级别以上且没有设置innodb_locks_unsafe_for_binlog那么并不会释放锁。

    update t set c2 = c2 + 1 where c1 >= 20;

    1:未更新其他索引列。

    加锁与上面SELECT… WHERE PK >= XX FOR UPDATE;一致。

    2:更新包含索引列。

    对主键c1=20加X锁,索引行加X锁,然后对后面的主键行加exclusive next-key lock(LOCK_X|LOCK_ORDINARY),同时对应的i_c2索引行加X锁,最后对表示记录上界的’supremum’加exclusive next-key lock。

    UPDATE … WHERE PK <= 20;

    1:未更新其他索引列。

    加锁与SELECT… WHERE PK <= XX FOR UPDATE;一致

    2:包含索引列。

    对pk<=20的主键满足条件记录加exclusive next-key lock(LOCK_X|LOCK_ORDINARY),同时对应的索引行加X锁。然后对pk>20的第一条记录加exclusive next-key lock,因不满足条件,因此server层查询停止。同样并不会释放后面记录上的锁。

    DELETE … WHERE PK >= 20;

    会对pk=20加X锁,对pk=20对应的索引加X锁,然后依次对pk>20的主键记录加exclusive next-key lock(LOCK_X|LOCK_ORDINARY),同时对应的索引行加X锁,最后对’supremum’加LOCK_X|LOCK_ORDINARY。

    查询条件为唯一索引等值

    SELECT … WHERE UK = XX FOR UPDATE;

    需要在索引记录上加X锁:LOCK_X|LOCK_REC_NOT_GAP,同时还要在对应主键行上加X锁。

    UPDATE … WHERE UK = XX;

    1:未更新其他索引列

    对唯一索引上数据加X锁(LOCK_X|LOCK_REC_NOT_GAP),然后对应的主键行也需要加X锁。

    2:更新其他索引列

    依次对唯一索引数据、主键行、索引数据加X锁。

    DELETE … WHERE UK = XX;

    会对唯一索引数据加X锁,根据唯一索引找到主键行后,会再依次对主键行、唯一索引、索引数据加X锁。

    查询条件为唯一索引范围

    SELECT … WHERE UK >= XX FOR UPDATE;

    1: 如果是通过主键,对满足条件的主键记录分别加exclusive next-key lock(LOCK_X|LOCK_ORDINARY),同时对上界’supremum’加exclusive next-key lock,锁住全部数据范围。

    2:如果是通过唯一索引,那么会对满足条件的索引记录分别加exclusive next-key lock,对应主键行加X锁,同时对索引上的’supremum’ record加exclusive next-key lock。

    SELECT … WHERE UN <= XX FOR UPDATE;

    对索引上满足条件的记录加exclusive next-key lock,对对应的主键行加X锁,然后对大于xx的第一条记录加exclusive next-key lock,且并不会去释放。

    UPDATE … WHERE UK >= XX;

    1:未包含索引列。

    等同上面指定走唯一索引的SELECT…FOR UPDATE语句加锁。

    2: 包含索引列。除了之前提到的主键和唯一索引加锁之外,对应被涉及到的索引行也要加X锁。

    UPDATE … WHERE UK <= XX FOR UPDATE;

    1:未包含索引列。

    会对索引上xx的第一条记录对应的索引行和主键行也会加X锁,同时不会释放。

    2:包含索引列

    会对索引上xx的第一条记录加exclusive next-key lock,对应主键行加X锁。

    DELETE … WHERE UK >= XX;

    走索引,会对>=xx的第一条记录加exclusive next-key lock,对应主键行加X锁,索引上数据行进行加X锁操作,对索引上’supremum’ record加exclusive next-key lock。

    查询条件为非唯一索引等值

    SELECT … WHERE IDX = XX FOR UPDATE;

    会对=xx在索引上加exclusive next-key lock(LOCK_X|LOCK_ORDINARY),对应主键加X锁(LOCK_X|LOCK_REC_NOT_GAP),然后在下一条记录上加exclusive gap lock(LOCK_X|LOCK_GAP)。即该语句会锁定范围。

    RR和RC隔离级别都要加的锁 

    唯一二级索引(uk)更新或插入

    UPDATE set uk=xxx… WHERE UK = XX;

    insert into ...uk =xxx

    如果UK原来存在,需要做去重判断,此时要对这个UK加S锁。比方说插入uk=1,表里已经有这个uk=1的记录,或者被删除还未被清理,需要先对uk=1这索引加S LOCK_ORDINARY锁再加X锁。更新UK,这等价于先删除再插入,一定有一条相同UK老记录,也需要先加S锁。

    原因在于代码逻辑中 row_ins_scan_sec_index_for_duplicate 重复检查要加S LOCK_ORDINARY锁。

    一个UK更新例子:

    1. CREATE TABLE `t_test` (
    2. `a` bigint(20) NOT NULL COMMENT 'a',
    3. `b` varchar(32) NOT NULL COMMENT 'b',
    4. `c` varchar(32) DEFAULT NULL COMMENT 'c',
    5. `d` int(11) NOT NULL COMMENT 'd',
    6. `e` bigint(20) NOT NULL COMMENT 'e',
    7. PRIMARY KEY (`a`),
    8. UNIQUE KEY `uk_b_d` (`b`,`d`) USING BTREE,
    9. KEY `idx_b_e` (`b`,`e`)
    10. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='test';
    11. insert into t_test values(1,'1','1',1,1);
    12. insert into t_test values(5,'5','5',5,5);
    13. insert into t_test values(10,'10','10',10,10);
    14. insert into t_test values(15,'15','15',15,15);
    15. update t_test set a = 7, b = "5", c = "5", d = 5, e = 7 where b = "5" and d = 5;
    16. 锁情况:
    17. TRANSACTIONS
    18. ======================
    19. Trx id counter 1363
    20. Purge done for trx's n:o < 1363 undo n:o < 0 state: running but idle
    21. History list length 3
    22. Total number of lock structs in row lock hash table 2
    23. LIST OF TRANSACTIONS FOR EACH SESSION:
    24. ---TRANSACTION 421893635597552, not started
    25. 0 lock struct(s), heap size 1160, 0 row lock(s)
    26. ---TRANSACTION 421893635594896, not started
    27. 0 lock struct(s), heap size 1160, 0 row lock(s)
    28. ---TRANSACTION 1358, ACTIVE 6 sec
    29. 3 lock struct(s), heap size 1160, 2 row lock(s), undo log entries 1
    30. MySQL thread id 4, OS thread handle 140418654865152, query id 99 localhost 127.0.0.1 root
    31. TABLE LOCK table `test`.`t_test` trx id 1358 lock mode IX
    32. RECORD LOCKS space id 24 page no 4 n bits 72 index uk_b_d of table `test`.`t_test` trx id 1358 lock_mode X locks rec but not gap
    33. RECORD LOCKS space id 24 page no 3 n bits 72 index PRIMARY of table `test`.`t_test` trx id 1358 lock_mode X locks rec but not gap
    34. ======================

  • 相关阅读:
    有没有简单好用的换天空背景软件推荐?
    二进制安装Kubernetes(k8s)v1.29.2
    SpringBoot SpringBoot 开发实用篇 5 整合第三方技术 5.14 SpringBoot 整合 quartz
    【搜题公众号】详解搜题公众号搭建教程(附赠题库)
    【GCC编译优化系列】前后编译的两个版本固件bin大小不一样,怎么办?
    python>>numpy(第二讲)
    Python中使用EMD(经验模态分解)
    FreeRTOS队列(queue)
    快手版Sora「可灵」开放测试:生成超120s视频,更懂物理,复杂运动也能精准建模...
    python socket 传输opencv读取的图像
  • 原文地址:https://blog.csdn.net/postgres20/article/details/127846586