欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Mysql 死锁过程及案例详解之记录锁与间隔锁Record Lock Gap Lock

程序员文章站 2022-04-17 14:38:47
...

记录锁Record Lock与间隔锁GAP Lock

记录锁Record Lock

记录锁Record Locks又称为行锁,它同时包含索引和间隔锁。记录锁可以是共享锁也可能是排他锁。可以通过performance_schema.data_locks表的LOCK_TYPE和LOCK_MODEL来查看。

示意案例

-- Step1 开始事务,更新city的Population字段,这里更新时通过辅助索引里的字段CountryCode。
START TRANSACTION;
UPDATE city
SET Population = Population + 1
WHERE CountryCode = 'LUX';

-- Step2 查看锁情况。
mysql> SELECT * FROM performance_schema.data_locks \G
*************************** 1. row ***************************
               ENGINE: INNODB
       ENGINE_LOCK_ID: 139740117241856:1067:139740049392448
ENGINE_TRANSACTION_ID: 38181
            THREAD_ID: 49
             EVENT_ID: 130
        OBJECT_SCHEMA: world
          OBJECT_NAME: city
       PARTITION_NAME: NULL
    SUBPARTITION_NAME: NULL
           INDEX_NAME: NULL
OBJECT_INSTANCE_BEGIN: 139740049392448
            LOCK_TYPE: TABLE
            LOCK_MODE: IX
          LOCK_STATUS: GRANTED
            LOCK_DATA: NULL
*************************** 2. row ***************************
               ENGINE: INNODB
       ENGINE_LOCK_ID: 139740117241856:6:20:344:139740049389536
ENGINE_TRANSACTION_ID: 38181
            THREAD_ID: 49
             EVENT_ID: 130
        OBJECT_SCHEMA: world
          OBJECT_NAME: city
       PARTITION_NAME: NULL
    SUBPARTITION_NAME: NULL
           INDEX_NAME: CountryCode
OBJECT_INSTANCE_BEGIN: 139740049389536
            LOCK_TYPE: RECORD
            LOCK_MODE: X
          LOCK_STATUS: GRANTED
            LOCK_DATA: 'LUX', 2452
*************************** 3. row ***************************
               ENGINE: INNODB
       ENGINE_LOCK_ID: 139740117241856:6:23:104:139740049389880
ENGINE_TRANSACTION_ID: 38181
            THREAD_ID: 49
             EVENT_ID: 130
        OBJECT_SCHEMA: world
          OBJECT_NAME: city
       PARTITION_NAME: NULL
    SUBPARTITION_NAME: NULL
           INDEX_NAME: PRIMARY
OBJECT_INSTANCE_BEGIN: 139740049389880
            LOCK_TYPE: RECORD
            LOCK_MODE: X,REC_NOT_GAP
          LOCK_STATUS: GRANTED
            LOCK_DATA: 2452
*************************** 4. row ***************************
               ENGINE: INNODB
       ENGINE_LOCK_ID: 139740117241856:6:20:326:139740049390224
ENGINE_TRANSACTION_ID: 38181
            THREAD_ID: 49
             EVENT_ID: 130
        OBJECT_SCHEMA: world
          OBJECT_NAME: city
       PARTITION_NAME: NULL
    SUBPARTITION_NAME: NULL
           INDEX_NAME: CountryCode
OBJECT_INSTANCE_BEGIN: 139740049390224
            LOCK_TYPE: RECORD
            LOCK_MODE: X,GAP
          LOCK_STATUS: GRANTED
            LOCK_DATA: 'LVA', 2434

通过结果不难发现这里的意向锁是排他锁,状态是“GRANTED”,


第二行里显示next-key锁在索引CountryCode上,LOCK_TYPE为记录。这里结合当前的WHERE条件对应的是锁定的数据为“’LUX’, 2452”,这里的2452对应的是主键的ID。
这里不难验证city表里CountryCode为’LUX’的主键即ID为2452。

第三行显示的锁的主键信息,其中LOCK_TYPE为记录、LOCK_MODEL为X,REC_NOT_GAP即是个排他锁,但不是间隔锁。

第四行显示的间隔锁的信息(主键或者索引里两个记录之间的间隔),详见间隔锁里的说明。

-- Step3 验证完后还原事务。
Rollback

间隔锁Gap Lock

间隔锁Gap Locks会锁定两条记录之间的空间,这种锁往往出现在聚集索引(即主键)和辅助索引(非聚集索引的其它索引的统称)里。

在索引页的第一条记录之前和该页的最后一条记录之后,分别有伪记录,称为下界记录(infimum record)和上界记录(supremum record)。

回到记录锁(Record Locks)里的例子,即将city表里CountryCode 是’LUX'的记录(仅有1条)里的Population字段进行更新,加1。如果在执行这个操作的时候,往表city里插入了一条或多条CountryCode 是’LUX'的新记录,会出现什么问题呢?如果UPDATE先执行,INSERT后执行则没有问题,如果反过来过来则会出现不一致的情况。即新插入的数据在后面又被更新掉了(这和预期不符)。所以这时需要用到间隔锁(Gap Lock),它的作用就是锁定当前索引页和下一个索引页这段间隔记录,即如果有INSERT,那么必须得之前的UPDATE执行完了才能操作。

在记录锁的示意案例的第4行里,我们可以看到间隔锁的相关信息,这里锁定的是索引页的‘LVA’, 2434,通过查询可以知道索引CountryCode的LUX的下一个即是LVA,所以这里锁定的是索引里对应这条记录。即在“LUX”“LVA”之间的记录加了排他的间隔锁。当然插入CountryCode为LVA的记录是可以的。

和间隔锁相关的锁有next-key locks和predicate locks.next-key锁时记录锁和间隔锁的组合(详见记录锁里的示例案例)。

*************************** 2. row ***************************

               ENGINE: INNODB

       ENGINE_LOCK_ID: 139740117241856:6:20:344:139740049389536

ENGINE_TRANSACTION_ID: 38181

            THREAD_ID: 49

             EVENT_ID: 130

        OBJECT_SCHEMA: world

          OBJECT_NAME: city

       PARTITION_NAME: NULL

    SUBPARTITION_NAME: NULL

           INDEX_NAME: CountryCode

OBJECT_INSTANCE_BEGIN: 139740049389536

            LOCK_TYPE: RECORD

            LOCK_MODE: X

          LOCK_STATUS: GRANTED

            LOCK_DATA: 'LUX', 2452

*************************** 3. row ***************************

               ENGINE: INNODB

       ENGINE_LOCK_ID: 139740117241856:6:23:104:139740049389880

ENGINE_TRANSACTION_ID: 38181

            THREAD_ID: 49

             EVENT_ID: 130

        OBJECT_SCHEMA: world

          OBJECT_NAME: city

       PARTITION_NAME: NULL

    SUBPARTITION_NAME: NULL

           INDEX_NAME: PRIMARY

OBJECT_INSTANCE_BEGIN: 139740049389880

            LOCK_TYPE: RECORD

            LOCK_MODE: X,REC_NOT_GAP

          LOCK_STATUS: GRANTED

            LOCK_DATA: 2452

*************************** 4. row ***************************

               ENGINE: INNODB

       ENGINE_LOCK_ID: 139740117241856:6:20:326:139740049390224

ENGINE_TRANSACTION_ID: 38181

            THREAD_ID: 49

             EVENT_ID: 130

        OBJECT_SCHEMA: world

          OBJECT_NAME: city

       PARTITION_NAME: NULL

    SUBPARTITION_NAME: NULL

           INDEX_NAME: CountryCode

OBJECT_INSTANCE_BEGIN: 139740049390224

            LOCK_TYPE: RECORD

            LOCK_MODE: X,GAP

          LOCK_STATUS: GRANTED

            LOCK_DATA: 'LVA', 2434

这里第2行即是next-key锁(InnoDB默认的锁类型),而第3行是在主键上的记录锁,第行是“LUX”到“LVA”之间的间隔锁。

predicate locks类似于间隔锁gap lock,它主要应用于使用坐标的空间场景里。通过使用该锁锁定查询中的最小边界矩形(minimum bounding rectangle)简称MBR,这样能防止对最小边界矩形内的数据进行更改已达到数据一致的目的。

示意案例

同记录锁。