• 记录锁(Record LockRecord)、间隙锁(Gap Lock)与 NextKey Lock Linux运维


    1. Record LockRecord

    Lock 也就是我们所说的记录锁,记录锁是对索引记录的锁,注意,它是针对索引记录,即它只锁定记录这一行数据

    1.将系统变量 innodb_status_output_locks 设置为 默认为OFF

    mysql> show variables like 'innodb_status_output_locks';
    +----------------------------+-------+
    | Variable_name | Value |
    +----------------------------+-------+
    | innodb_status_output_locks | OFF |
    +----------------------------+-------+
    1 row in set (0.00 sec),

    mysql> set global innodb_status_output_locks=ON;
    Query OK, 0 rows affected (0.00 sec)

    mysql> show variables like 'innodb_status_output_locks';
    +----------------------------+-------+
    | Variable_name | Value |
    +----------------------------+-------+
    | innodb_status_output_locks | ON |
    +----------------------------+-------+
    1 row in set (0.00 sec)

    2.执行如下 SQL,锁定一行数据,此时会自动为表加上 IX 锁:

    mysql> begin;    #开始事务
    Query OK, 0 rows affected (0.00 sec)

    mysql> select * from city where id=1 for update;     
    +----+-------+-------------+----------+------------+
    | ID | Name | CountryCode | District | Population |
    +----+-------+-------------+----------+------------+
    | 1 | Kabul | AFG | Kabol | 1000 |
    +----+-------+-------------+----------+------------+
    1 row in set (0.00 sec)

    mysql> show engine innodb status\G;                  #查看 InnoDB 存储引擎的情况
    *************************** 1. row ***************************
    Type: InnoDB
    Name:
    Status:
    =====================================
    2022-05-07 19:07:18 139841320953600 INNODB MONITOR OUTPUT
    =====================================
    Per second averages calculated from the last 9 seconds
    -----------------
    BACKGROUND THREAD
    -----------------
    srv_master_thread loops: 1 srv_active, 0 srv_shutdown, 1284 srv_idle
    srv_master_thread log flush and writes: 0
    ----------
    SEMAPHORES
    ----------
    OS WAIT ARRAY INFO: reservation count 1
    OS WAIT ARRAY INFO: signal count 1
    RW-shared spins 0, rounds 0, OS waits 0
    RW-excl spins 0, rounds 0, OS waits 0
    RW-sx spins 0, rounds 0, OS waits 0
    Spin rounds per wait: 0.00 RW-shared, 0.00 RW-excl, 0.00 RW-sx
    ------------
    TRANSACTIONS                                      #主要关注这里的TRANSACTIONS
    ------------
    Trx id counter 18190
    Purge done for trx's n:o < 18183 undo n:o < 0 state: running but idle
    History list length 0
    LIST OF TRANSACTIONS FOR EACH SESSION:
    ---TRANSACTION 421316317542832, not started
    0 lock struct(s), heap size 1128, 0 row lock(s)
    ---TRANSACTION 421316317542024, not started
    0 lock struct(s), heap size 1128, 0 row lock(s)
    ---TRANSACTION 18189, ACTIVE 4 sec
    2 lock struct(s), heap size 1128, 1 row lock(s)
    MySQL thread id 8, OS thread handle 139841320953600, query id 25 localhost root starting
    show engine innodb status
    TABLE LOCK table `world`.`city` trx id 18189 lock mode IX     **************
    RECORD LOCKS space id 5 page no 6 n bits 248 index PRIMARY of table `world`.`city` trx id 18189 lock_mode X locks rec but not gap
    Record lock, heap no 2 PHYSICAL RECORD: n_fields 7; compact format; info bits 0  ********
    0: len 4; hex 80000001; asc ;;
    1: len 6; hex 00000000430e; asc C ;;
    2: len 7; hex 02000001140151; asc Q;;
    3: len 30; hex 4b6162756c20202020202020202020202020202020202020202020202020; asc Kabul ; (total 35 bytes);
    4: len 3; hex 414647; asc AFG;;
    5: len 20; hex 4b61626f6c202020202020202020202020202020; asc Kabol ;;
    6: len 4; hex 800003e8; asc ;;

    --------

    TABLE LOCK table `world`.`city` trx id 18189 lock mode IX

    这句就是说事务 id 为 18189 的事务,为 city 表添加了意向排他锁(IX)。

    RECORD LOCKS space id 5 page no 6 n bits 248 index PRIMARY of table `world`.`city` trx id 18189 lock_mode X locks rec but not gap
    Record lock, 这句就是一个锁结构的记录,这里的索引是 PRIMARY,加的锁也是正儿八经的记录锁(not gap)。

    LOCKS REC BUT NOT GAP,有为句就说明这是一个记录锁

    Record Lock 和我们之前所讲的 S 锁以及 X 锁有什么区别呢?S 锁是共享锁,X 锁是排他锁,当我们加 S 锁或者 X 锁的时候,如果用到了索引,锁加在了某一条具体的记录上,那么这个锁也是一个记录锁(其实,记录锁,S 锁,X 锁,概念有一些重复的地方,但是描述的重点不一样)。

    2. Gap Lock

    Gap Lock 也叫做间隙锁,它的存在可以解决幻读问题,另外需要注意,Gap Lock 也只在 REPEATABLE READ 隔离级别下有效。先来看看什么是幻读,我们来看如下一个表格:

        有两个会话,A 和 B,先在会话 A 中开启事务,然后查询 age 为 99 的用户总数,注意使用当前读,因为在默认的隔离级别下,默认的快照读并不能读到其他事务提交的数据,至于快照读和当前读的区别。当会话 A 中第一次查询过后,会话 B 中向数据库添加了一行记录,等到会话 A 中第二次查询的时候,就查到了和第一次查询不一样的结果,这就是幻读(注意幻读专指数据插入引起的不一致)。

        在 MySQL  8.0默认的隔离级别 REPEATABLE READ 下,上图所描述的情况无法复现。无法复现的原因在于,在 MySQL 的 REPEATABLE READ 隔离级别中,它已经帮我们解决了幻读问题,解决的方案就是 Gap Lock。

        之所以出现幻读的问题,是因为记录之间存在缝隙,用户可以往这些缝隙中插入数据,这就导致了幻读问题,如下图:

    id 之间有缝隙,有缝隙就有漏洞。前面我们所说的记录锁只能锁住一条具体的记录,但是对于记录之间的空隙却无能无力,这就导致了幻读(其他事务可往缝隙中插入数据)。现在 Gap Lock 间隙锁,就是要把这些记录之间的间隙也给锁住,间隙锁住了,就不用担心幻读问题了,这也是 Gap Lock 存在的意义。

    给一条记录加 Gap Lock,是锁住了这条记录前面的空隙,例如给 id 为 1 的记录加 Gap Lock,锁住的范围是 (-∞,1),给 id 为 3 的记录加 Gap Lock,锁住的范围是 (1,3),那么 id 为 10 后面的空隙怎么锁定呢?MySQL 提供了一个 Supremum 表示当前页面中的最大记录,所以最后针对 Supremum 锁住的范围就是 (10,+∞),这样,所有的间隙都被覆盖到了,由于锁定的是间隙,所以都是开区间。

    简单的例子:
    1.创建一个表:

    mysql> CREATE TABLE `T4` (
    -> `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
    -> `username` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
    -> `age` int(11) DEFAULT NULL,
    -> PRIMARY KEY (`id`),
    -> KEY `age` (`age`)
    -> ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
    Query OK, 0 rows affected, 2 warnings (0.03 sec)

    插入以下数据:

    mysql> insert into T4 values('1','xiaoli',99);
    Query OK, 1 row affected (0.00 sec)

    mysql> insert into T4 values('2','xiaoming',89);
    Query OK, 1 row affected (0.00 sec)

    mysql> insert into T4 values('3','xiaoliu',79);
    Query OK, 1 row affected (0.00 sec)

    mysql> select * from T4;
    +----+----------+------+
    | id | username | age |
    +----+----------+------+
    | 1 | xiaoli | 99 |
    | 2 | xiaoming | 89 |
    | 3 | xiaoliu | 79 |
    +----+----------+------+
    3 rows in set (0.00 sec)

    2.执行如下 SQL,锁定一行数据,此时也会产生间隙锁

    mysql> begin;
    Query OK, 0 rows affected (0.00 sec)

    mysql> select * from T4 force index(age) where age=89 for update;
    +----+----------+------+
    | id | username | age |
    +----+----------+------+
    | 2 | xiaoming | 89 |
    +----+----------+------+
    1 row in set (0.00 sec)

    3.查看innodb引擎状况

    mysql> show engine innodb status\G;
    *************************** 1. row ***************************
    Type: InnoDB
    Name:
    Status:
    =====================================
    2022-05-07 19:56:28 139841320953600 INNODB MONITOR OUTPUT
    =====================================
    Per second averages calculated from the last 22 seconds
    -----------------
    BACKGROUND THREAD
    -----------------
    srv_master_thread loops: 6 srv_active, 0 srv_shutdown, 4210 srv_idle
    srv_master_thread log flush and writes: 0
    ----------
    SEMAPHORES
    ----------
    OS WAIT ARRAY INFO: reservation count 3
    OS WAIT ARRAY INFO: signal count 3
    RW-shared spins 0, rounds 0, OS waits 0
    RW-excl spins 0, rounds 0, OS waits 0
    RW-sx spins 0, rounds 0, OS waits 0
    Spin rounds per wait: 0.00 RW-shared, 0.00 RW-excl, 0.00 RW-sx
    ------------
    TRANSACTIONS
    ------------
    Trx id counter 18208
    Purge done for trx's n:o < 18205 undo n:o < 0 state: running but idle
    History list length 0
    LIST OF TRANSACTIONS FOR EACH SESSION:
    ---TRANSACTION 421316317542832, not started
    0 lock struct(s), heap size 1128, 0 row lock(s)
    ---TRANSACTION 421316317542024, not started
    0 lock struct(s), heap size 1128, 0 row lock(s)
    ---TRANSACTION 18207, ACTIVE 58 sec
    4 lock struct(s), heap size 1128, 3 row lock(s)
    MySQL thread id 8, OS thread handle 139841320953600, query id 37 localhost root starting
    show engine innodb status
    TABLE LOCK table `world`.`T4` trx id 18207 lock mode IX
    RECORD LOCKS space id 8 page no 5 n bits 72 index age of table `world`.`T4` trx id 18207 lock_mode X   ****记录锁
    Record lock, heap no 3 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
    0: len 4; hex 80000059; asc Y;;
    1: len 4; hex 00000002; asc ;;

    RECORD LOCKS space id 8 page no 4 n bits 72 index PRIMARY of table `world`.`T4` trx id 18207 lock_mode X locks rec but not gap
    Record lock, heap no 3 PHYSICAL RECORD: n_fields 5; compact format; info bits 0   *****间隙锁

    一个间隙锁的加锁记录,可以看到,在某一个记录之前加了间隙锁。这就是间隙锁。

    非常重要注意:Gap Lock 只在 REPEATABLE READ 隔离级别下有效。

    3. Next-Key Lock

    既想锁定一行,又想锁定行之间的记录,那么就是 Next-Key Lock 了,换言之,Next-Key Lock 是 Record Lock 和 Gap Lock 的结合体。

    Next-Key Lock 的加锁规则:
    
    锁的范围是左开右闭。
    如果是唯一非空索引的等值查询,Next-Key Lock 会退化成 Record Lock。
    普通索引上的等值查询,向后遍历时,最后一个不满足等值条件的时候,Next-Key Lock 会退化成 Gap Lock。
    举例:
    创建一个学生表:

    mysql> CREATE TABLE `student` (
    -> `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
    -> `name` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
    -> `score` double NOT NULL,
    -> PRIMARY KEY (`id`),
    -> UNIQUE KEY `score` (`score`)
    -> ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
    Query OK, 0 rows affected, 1 warning (0.02 sec)

    插入数据:

    mysql> insert into student values ('1','a','90');
    Query OK, 1 row affected (0.00 sec)

    mysql> insert into student values ('2','b','89');
    Query OK, 1 row affected (0.00 sec)

    mysql> insert into student values ('3','c','95');
    Query OK, 1 row affected (0.01 sec)

    mysql> insert into student values ('4','d','80');
    Query OK, 1 row affected (0.00 sec)

    mysql> insert into student values ('5','e','79');
    Query OK, 1 row affected (0.01 sec)

      查看数据:

    mysql> select * from student;
    +----+------+-------+
    | id | name | score |
    +----+------+-------+
    | 1 | a | 90 |
    | 2 | b | 89 |
    | 3 | c | 95 |
    | 4 | d | 80 |
    | 5 | e | 79 |
    +----+------+-------+
    5 rows in set (0.00 sec)

    执行以下SQL:

    mysql> begin;
    Query OK, 0 rows affected (0.00 sec)

    mysql> select * from student force index(score) where score=90 for update;
    +----+------+-------+
    | id | name | score |
    +----+------+-------+
    | 1 | a | 90 |
    +----+------+-------+
    1 row in set (0.00 sec)

    查看引擎状况:

    mysql> show engine innodb status\G;
    *************************** 1. row ***************************
    Type: InnoDB
    Name:
    Status:
    =====================================
    2022-05-07 20:14:25 139841320953600 INNODB MONITOR OUTPUT
    =====================================
    Per second averages calculated from the last 40 seconds
    -----------------
    BACKGROUND THREAD
    -----------------
    srv_master_thread loops: 12 srv_active, 0 srv_shutdown, 5274 srv_idle
    srv_master_thread log flush and writes: 0
    ----------
    SEMAPHORES
    ----------
    OS WAIT ARRAY INFO: reservation count 9
    OS WAIT ARRAY INFO: signal count 9
    RW-shared spins 0, rounds 0, OS waits 0
    RW-excl spins 0, rounds 0, OS waits 0
    RW-sx spins 0, rounds 0, OS waits 0
    Spin rounds per wait: 0.00 RW-shared, 0.00 RW-excl, 0.00 RW-sx
    ------------
    TRANSACTIONS
    ------------
    Trx id counter 18231
    Purge done for trx's n:o < 18229 undo n:o < 0 state: running but idle
    History list length 0
    LIST OF TRANSACTIONS FOR EACH SESSION:
    ---TRANSACTION 421316317542832, not started
    0 lock struct(s), heap size 1128, 0 row lock(s)
    ---TRANSACTION 421316317542024, not started
    0 lock struct(s), heap size 1128, 0 row lock(s)
    ---TRANSACTION 18230, ACTIVE 51 sec
    3 lock struct(s), heap size 1128, 2 row lock(s)
    MySQL thread id 8, OS thread handle 139841320953600, query id 49 localhost root starting
    show engine innodb status
    TABLE LOCK table `world`.`student` trx id 18230 lock mode IX            ****************
    RECORD LOCKS space id 9 page no 5 n bits 72 index score of table `world`.`student` trx id 18230 lock_mode X locks rec but not gap
    Record lock, heap no 2 PHYSICAL RECORD: n_fields 2; compact format; info bits 0          **************************

    由于 score 是唯一非空索引,所以 Next-Key Lock 会退化成 Record Lock,换句话说,这行 SQL 只给 score 为 90 的记录加锁,不存在 Gap Lock,即我们新开一个会话,插入一条 score 为 88 的记录也是 OK 的。

    mysql> insert into student values ('6','f','88');
    Query OK, 1 row affected (0.00 sec)

    由于并不存在 score 为 91 的记录,所以这里会产生一个范围为 (90,95) 的间隙锁,我们执行如下 SQL 可以验证:

    mysql> begin;
    Query OK, 0 rows affected (0.00 sec)

    mysql> select * from student force index(score) where score=91 for update;
    Empty set (0.00 sec)

    另外一个会话中插入一个90.1的score,出现间隙锁(90~95),90~95数据都添加不进去。

    mysql> insert into student values ('7','g','90.1');
    ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

    mysql> insert into student values ('8','h','90');
    ERROR 1062 (23000): Duplicate entry '90' for key 'student.score'

    mysql> insert into student values ('9','j','94.9');
    ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

    mysql> insert into student values ('10','l','95.1');    ********插入95以上数据可以
    Query OK, 1 row affected (0.00 sec)

    总结:

    可以看到,90.1、94.9 都会被阻塞(我按了 Ctrl C,所以大家看到查询终止)。

    90、95 则不符合唯一非空索引的条件。

    95.1 则可以插入成功

    参考文献:

    https://www.51cto.com/article/707803.html

  • 相关阅读:
    重置SQLSERVER表的自增列,让自增列重新计数
    【PLM】【PDM】60页PPT终于说清了PDM和PLM的区别;智造时代,PLM系统10大应用趋势!
    数据库设计规范
    不要听吹牛逼什么前端MVVM框架就是好,其实都是一帮没学好分层设计的搞出来的,让你彻底看清前端MVVM的本质
    SQL数据库日志清理
    1.3、安装MySQL、MySQLdb、Pycharm和其他软件
    1.2.2.3、Django虚拟目录作用以及文件说明
    nginx设置图片防盗链和白名单
    php-5.5.7升级,编译配置方法
    mysql函数CONCAT_WS()比CONCAT()更方便
  • 原文地址:https://www.cnblogs.com/linuxmysql/p/16244062.html
Copyright © 2020-2023  润新知