InnoDB锁介绍

本文详细介绍了MySQLInnoDB引擎中的各种锁策略,包括读写锁、记录锁(RecordLocks)、GapLocks和Next-KeyLocks,以及它们在并发控制中的作用和示例。重点讨论了表锁和行锁的粒度、冲突情况以及性能影响。
摘要由CSDN通过智能技术生成

本文主要介绍MySQL InnoDB引擎中的各种锁策略和锁类别,并针对记录锁做演示以便于理解。

以下内容适用于MySQL 8.0版本。

读写锁

处理并发读/写访问的系统通常实现一个由两种锁类型组成的锁系统。这两种锁通常被称为共享锁(shared lock)和排他锁(exclusive lock),也叫读锁(read lock)和写锁(write lock)。

读锁

资源上的读锁是共享的,或者说是相互不阻塞的。多个客户端可以同时读取同一个资源而互不干扰。

写锁

写锁则是排他的,也就是说,一个写锁既会阻塞读锁也会阻塞其他的写锁,这是出于安全策略的考虑,只有这样才能确保在特定的时间点只有一个客户端能执行写入,并防止其他客户端读取正在写入的资源。

锁粒度

一种提高共享资源并发性的方式就是让锁定对象更有选择性。尽量只锁定包含需要修改的部分数据,而不是所有的资源。但是锁的范围越小,管理锁的逻辑就会越复杂,开销也越大。因此锁定策略是锁开销和数据安全性之间的平衡,这种平衡会影响性能。MySQL提供了两种粒度的锁。

表锁

InnoDB的表级别锁包含五种锁模式:LOCK_IS、LOCK_IX、LOCK_X、LOCK_S以及LOCK_AUTO_INC锁。

LOCK_IS/LOCK_IX

也就是所谓的意向锁,这实际上可以理解为一种“暗示”未来需要什么样行级锁,IS表示未来可能需要在这个表的某些记录上加共享锁,IX表示未来可能需要在这个表的某些记录上加排他锁。意向锁是表级别的,IS和IX锁之间相互并不冲突,但与表级S/X锁冲突。

在对记录加S锁或者X锁时,必须保证其在相同的表上有对应的意向锁或者锁强度更高的表级锁。

LOCK_X

当加了LOCK_X表级锁时,所有其他的表级锁请求都需要等待。X锁的几个情况:

  • DDL操作的最后一个阶段(ha_innobase::commit_inlace_alter_table)对表上加LOCK_X锁,以确保没有别的事务持有表级锁。通常情况下Server层MDL锁已经能保证这一点了,在DDL的commit 阶段是加了排他的MDL锁的。但诸如外键检查或者刚从崩溃恢复的事务正在进行某些操作,这些操作都是直接InnoDB自治的,不走server层,也就无法通过MDL所保护;

  • 当设置会话的autocommit变量为OFF时,执行LOCK TABLE tbname WRITE这样的操作会加表级的LOCK_X锁(ha_innobase::external_lock);

  • 对某个表空间执行discard或者import操作时,需要加LOCK_X锁(ha_innobase::discard_or_import_tablespace)。

LOCK_S
  • 在DDL的第一个阶段,如果当前DDL不能通过ONLINE的方式执行,则对表加LOCK_S锁(prepare_inplace_alter_table_dict);

  • 设置会话的autocommit为OFF,执行LOCK TABLE tbname READ时,会加LOCK_S锁(ha_innobase::external_lock)。

当客户端想对表进行写操作(插入、删除、更新等)时,需要先获得一个写锁,这会阻塞其他客户端对该表的所有读写操作。只有没有人执行写操作时,其他读取的客户端才能获得读锁,读锁之间不会相互阻塞。

表锁的冲突和兼容情况

XIXSIS
XConflictConflictConflictConflict
IXConflictCompatibleConflictCompatible
SConflictConflictCompatibleCompatible
ISConflictCompatibleCompatibleCompatible

从上面的描述我们可以看到LOCK_X及LOCK_S锁在实际的大部分负载中都很少会遇到。主要还是互相不冲突的LOCK_IS及LOCK_IX锁。一个有趣的问题是,每次加表锁时,却总是要扫描表上所有的表级锁对象,检查是否有冲突的锁。很显然,如果我们在同一张表上的更新并发度很高,这个链表就会非常长。

基于大多数表锁不冲突的事实,我们在RDS MYSQL中对各种表锁对象进行计数,在检查是否有冲突时,例如当前申请的是意向锁,如果此时LOCK_S和LOCK_X的锁计数都是0,就可以认为没有冲突,直接忽略检查。由于检查是在持有全局大锁lock_sys->mutex下进行的。在单表大并发下,这个优化的效果还是非常明显的,可以减少持有全局大锁的时间。

LOCK_AUTO_INC

AUTO_INC锁加在表级别,和AUTO_INC、表级S锁以及X锁不相容。锁的范围为SQL级别,SQL结束后即释放。

行锁

使用行级锁(row lock)可以最大程度地支持并发处理(也带来了最大的锁开销)。行级锁是在存储引擎而不是服务器中实现的。行锁依赖索引加锁,如果表没有索引,InnoDB会将锁加在隐藏的聚簇索引上。

Record Locks(行记录锁)

表示这个锁对象只是单纯的锁在记录上,不会锁记录之前的 GAP。比如利用唯一索引查询表中存在的一条记录。

Gap Locks(间隙锁)

间隙锁,锁定一个范围,但不包含这个范围中的行记录。

Next-Key Locks

上面两种锁的结合,锁定一个范围和这个范围内的行记录本身。目标是解决幻读问题(Phantom Problem)。

Record Locks演示

表结构

CREATE TABLE `tx_demo` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `age` int DEFAULT NULL,
  `name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_age` (`age`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

数据

id

age

name

15

5

agc

16

8

agc

17

12

agc

19

13

agc

行记录锁演示

  1. 开始事务后执行SQL

    -- 开始事务后执行SQL
    select * from tx_demo where id = 19 for update;
  2. 查看持有锁的情况

    -- 查看持有锁的情况
    SELECT 
        thread_id,
        INDEX_NAME,
        LOCK_TYPE,
        LOCK_MODE,
        LOCK_STATUS,
        LOCK_DATA
    FROM
        performance_schema.data_locks;

  3. 持有锁的情况 

如上所示,该事务当前持有的锁是索引记录19,LOCK_MODE是X,REC_NOT_GAP。原因是该索引是唯一索引,并且该查询能检索出唯一一条数据。

Gap Locks演示
  1. 开始事务后执行SQL

    -- 开始事务后执行SQL
    select * from tx_demo where age = 10 for update;
  2. 查看持有锁的情况

    -- 查看持有锁的情况
    SELECT 
        thread_id,
        INDEX_NAME,
        LOCK_TYPE,
        LOCK_MODE,
        LOCK_STATUS,
        LOCK_DATA
    FROM
        performance_schema.data_locks;
  3. 持有锁的情况

如上所示,因为表中没有age=10的记录,因此Gap Locks锁定了(8,12)这个区间,注意,不包括12个索引记录。

Next-Key Locks演示
  1. 开始事务后执行SQL

    -- 开始事务后执行SQL
    select * from tx_demo where age = 8 for update;

  2. 查看持有锁的情况
    -- 查看持有锁的情况
    SELECT 
        thread_id,
        INDEX_NAME,
        LOCK_TYPE,
        LOCK_MODE,
        LOCK_STATUS,
        LOCK_DATA
    FROM
        performance_schema.data_locks;
  3. 持有锁的情况

 

如上所示,在索引idx_age上,这里涉及到的Record Locks是索引记录8,涉及到的Gap Locks有两个,分别是(5,8)、(8,12),因此结合起来就是Next-Key Locks (5,8]和Gap Locks(8,12)。 表中Next-Key Locks (5,8]的表示就是LOCK_DATA为8,16那一行,LOCK_MODE是X;表中Gap Locks(8,12)的表示就是LOCK_DATA为12,17那一行,LOCK_MODE是X,REC_NOT_GAP

LOCK_MODE的含义

MySQL表performance_schema.data_locks中LOCK_MODE列的含义

  • LOCK_MODE = X,说明是 X 型的 next-key 锁;

  • LOCK_MODE = X, REC_NOT_GAP,说明是 X 型的记录锁;

  • LOCK_MODE = X, GAP,说明是 X 型的间隙锁;

  • LOCK_MODE = AUTO_INC,说明是表级自增锁;

  • LOCK_MODE = UNKNOWN,说明是未知模式锁;

参考文档

https://dev.mysql.com/doc/refman/8.0/en/innodb-locking.html

https://dev.mysql.com/doc/refman/8.0/en/performance-schema-data-locks-table.html

https://xiaolincoding.com/mysql/lock/show_lock.html#%E5%87%86%E5%A4%87%E5%B7%A5%E4%BD%9C

http://mysql.taobao.org/monthly/2016/01/01/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值