7802_Mysql数据库锁分类

MySQL 的锁可以按照不同的维度进行分类,下图清晰地展示了其锁的层次结构:

flowchart TD A[MySQL 锁] --> B["按粒度分<br>(Granularity)"] A --> C["按模式分<br>(Mode)"] A --> D["按思想分<br>(Philosophy)"] B --> B1[全局锁] B --> B2[表级锁] B --> B3[行级锁] B2 --> B2_1[表锁 Table Lock] B2 --> B2_2[元数据锁 MDL] B2 --> B2_3[意向锁 Intention Lock] B3 --> B3_1[记录锁 Record Lock] B3 --> B3_2[间隙锁 Gap Lock] B3 --> B3_3[临键锁 Next-Key Lock] B3 --> B3_4[插入意向锁 Insert Intention Lock] C --> C1[共享锁 S Lock] C --> C2[排他锁 X Lock] D --> D1[悲观锁] D --> D2[乐观锁]

接下来,我们详细解读图中的每一种锁。

一、按锁的粒度分类

锁的粒度指的是锁定的数据范围大小。粒度越小,并发度越高,但管理越复杂。

1. 全局锁

  • 作用范围:整个数据库实例。
  • 实现方式:使用命令 FLUSH TABLES WITH READ LOCK (FTWRL)。
  • 效果:使整个数据库处于只读状态。所有数据更新语句(DML)、数据定义语句(DDL)和更新类事务的提交语句都会被阻塞。
  • 用途:主要用于全库逻辑备份。但请注意,在支持事务的引擎(如 InnoDB)中,更推荐使用 mysqldump --single-transaction 来获得一致性视图,而不需要锁定整个库。

2. 表级锁

  • 作用范围:整张表。
  • 种类
    • 表锁 (Table Lock)
      • 通过 LOCK TABLES table_name READ/WRITE 手动加锁。
      • 读锁(共享锁):阻止其他会话写入,但允许读取。
      • 写锁(排他锁):阻止其他会话的读写。
      • 开销小,加锁快;但粒度大,并发度低。InnoDB 很少使用。
    • 元数据锁 (Metadata Lock, MDL)
      • MySQL 5.5 引入,用于保证读写的正确性。
      • 自动加锁,无需手动操作。
      • 当对一个表做 CRUD 操作(DML)时,加 MDL 读锁;当要对表做结构变更操作(DDL)时,加 MDL 写锁
      • 读锁之间不互斥,多个线程可以同时读。
      • 读写锁、写锁之间互斥,DDL 操作会阻塞并发的 CRUD 操作,反之亦然。
    • 意向锁 (Intention Lock)
      • InnoDB 特有,是一种表级锁,但它标志着事务稍后会对表中的施加哪种类型的锁。
      • 目的:为了高效协调表锁和行锁的冲突。例如,当你想给一个表加表写锁时,数据库需要检查是否有其他事务已经持有了该表中某行的行锁。意向锁使得这个过程不需要逐行检查。
      • 意向共享锁 (IS):事务打算给某些行加共享锁(S Lock)。
      • 意向排他锁 (IX):事务打算给某些行加排他锁(X Lock)。
      • 冲突矩阵
        请求锁类型 IS IX S X
        IS 兼容 兼容 兼容 冲突
        IX 兼容 兼容 冲突 冲突
        S 兼容 冲突 兼容 冲突
        X 冲突 冲突 冲突 冲突

3. 行级锁

MYSQL的数据

    --表结构
    CREATE TABLE `users` (
      `id` bigint(20) NOT NULL COMMENT '用户ID',
      `code` varchar(100) DEFAULT NULL COMMENT '用户编号',
      `name` varchar(100) DEFAULT NULL COMMENT '用户姓名',
      PRIMARY KEY (`id`),
      KEY `users_code_IDX` (`code`) USING BTREE
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
    --数据
    INSERT INTO users(id, code, name)
    VALUES(1, '0001', '张三');
    INSERT INTO users(id, code, name)
    VALUES(10, '0010', '李四');
    INSERT INTO users(id, code, name)
    VALUES(20, '0020', '王五');
    INSERT INTO users(id, code, name)
    VALUES(100, '0100', '赵六');

  • 作用范围:单行或多行记录。
  • 说明InnoDB 引擎支持的行级锁是基于索引实现的。如果查询没有用到索引,会退化为表锁。
    • 例如,SELECT * FROM users WHERE name='小王' for update; 因为没有命中索引,所以会锁住全表,阻止 id=2 的新记录插入。

      用户1:
      -- 开始事务
      START TRANSACTION;
      -- 执行一些 SQL 语句
      SELECT * FROM users WHERE name='小王' for update;
      用户2:
      INSERT INTO users(id, code, name)
      VALUES(2, '0002', '小李');
      
      
  • 种类
    • 记录锁 (Record Lock)

      • 锁住索引的一条具体记录
      • 防止其他事务对此条记录进行修改或删除。
      • 例如,SELECT * FROM users WHERE id=10 for update; 会锁住 id=10的记录,阻止 id=10 的记录更新。
      用户1:
      -- 开始事务
      START TRANSACTION;
      -- 执行一些 SQL 语句
      SELECT * FROM users WHERE id=10 for update;
      用户2:
      update users set name='李四1' where id=10;
      
    • 间隙锁 (Gap Lock)

      • 锁住索引记录之间的间隙(一个开区间),防止其他事务在这个间隙中插入新的记录。
      • 解决了“幻读”问题。
      • 例如,SELECT * FROM users WHERE id BETWEEN 10 AND 20 for update; 会锁住 id=10 到 id=20 之间的所有间隙,阻止 id=15 的新记录插入。
      用户1:
      -- 开始事务
      START TRANSACTION;
      -- 执行一些 SQL 语句
      SELECT * FROM users WHERE id =15 for update;
      用户2:
      INSERT INTO users(id, code, name)
      VALUES(15, '0015', '王八');
      
      
    • 临键锁 (Next-Key Lock)

      • 记录锁 + 间隙锁 的组合。它既锁住记录本身,也锁住该记录之前的间隙。
      • 它是一个左开右闭的区间。例如,假设数据库中有索引记录 1, 10, 20, 100,那么临键锁可能锁定的区间有:(-∞, 1], (1, 10], (10, 20], (20, +∞)
      • 这是 InnoDB 默认的行锁算法,它同时解决了“不可重复读”和“幻读”的问题。
      • 例如,SELECT * FROM users WHERE code='0020' for update; 会锁住 (10, 20]间隙,阻止 id=11 的新记录插入。
      用户1:
      -- 开始事务
      START TRANSACTION;
      -- 执行一些 SQL 语句
      SELECT * FROM users WHERE code='0020' for update;
      用户2:
      INSERT INTO users(id, code, name)
      VALUES(11, '0011', '小李');
      
      

二、按锁的模式分类

1. 共享锁 (Shared Lock, S Lock)

  • 又称为“读锁”。
  • 允许多个事务同时读取同一资源。
  • 如果一个事务持有了共享锁,其他事务可以继续加共享锁,但不能加排他锁
  • 手动加锁:SELECT ... LOCK IN SHARE MODE; (旧语法) 或 SELECT ... FOR SHARE; (MySQL 8.0+)。

2. 排他锁 (Exclusive Lock, X Lock)

  • 又称为“写锁”。
  • 允许持有锁的事务更新或删除数据。
  • 如果一个事务持有了排他锁,其他事务不能再加任何类型的锁(共享锁或排他锁),直到它释放。
  • 手动加锁:SELECT ... FOR UPDATE;
  • InnoDB 会在执行 UPDATE、DELETE 语句时自动给涉及的行加排他锁

三、按锁的思想分类(非数据库内置,是应用策略)

1. 悲观锁

  • 思想:总是假设最坏的情况,认为拿数据的时候别人会修改,所以每次操作数据时都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。
  • 实现依靠数据库自身的锁机制实现,例如 SELECT ... FOR UPDATE
  • 适用场景:写多读少,竞争激烈的场景。

2. 乐观锁

  • 思想:总是假设最好的情况,认为拿数据的时候别人不会修改,所以不上锁。但在更新的时候会判断一下在此期间别人有没有去更新这个数据。
  • 实现不在数据库中加锁,而是在表中增加一个版本号(version)字段或时间戳。更新时,检查版本号是否和之前读取的一致。
    UPDATE table_name 
    SET column = new_value, version = current_version + 1 
    WHERE id = #{id} AND version = #{old_version};
    
  • 适用场景:读多写少,竞争不激烈的场景。

总结

锁类型 主要特点 用途 支持引擎
行级锁 粒度小,并发高,管理复杂 解决事务间的行级冲突 InnoDB
表级锁 粒度大,并发低,管理简单 全表操作、DDL 协调 所有引擎
全局锁 锁定整个库 全库备份 所有引擎
共享锁 (S) 允许读,阻止写 保证读取一致性 InnoDB
排他锁 (X) 阻止读写 保证更新排他性 InnoDB
悲观锁 先加锁,后操作 高竞争写环境 通过 DB 锁实现
乐观锁 不加锁,更新时校验 低竞争写环境 应用层实现

理解这些锁的区别和适用场景,对于设计高性能、高并发的数据库应用以及排查解决锁冲突问题至关重要。在 InnoDB 中,最常用和最需要重点关注的是行级锁元数据锁 (MDL)

posted @ 2025-09-03 21:20  庞去广  阅读(15)  评论(0)    收藏  举报