Mysql中的锁机制

1. 锁的介绍

在数据库系统中,锁(Lock) 是一种用于控制并发访问的机制,目的是保证数据的一致性和完整性。当多个事务同时操作同一数据时,锁可以防止出现脏读、不可重复读、幻读等问题。

MySQL 的锁机制与其存储引擎密切相关,InnoDB 是目前最常用的事务型存储引擎,支持行级锁和多种高级锁类型;而 MyISAM 仅支持表级锁。

2.锁的分类

1. 按锁的粒度分类

全局锁

全局锁是最高级别的锁,其作用是将整个数据库进行加锁操作,不允许再对这个数据库进行操作(如DDL或者DML)。使用的场景通常为数据库备份或者数据库复制时,不允许事务对数据库的数据进行修改。使用全局锁主要由以下两种方式:

  • 使用FTWRL

    1
    FLUSH TABLES WITH READ LOCK;
  • 使用mysqldump自动加锁(更推荐)

    1
    mysqldump --single-transaction --master-data=2 -u root -p dbname > backup.sql

表锁

表锁时Mysql数据库中粒度最粗的锁,它的作用是对整个表加锁(而不是数据表中的某一行),其他事务想要对这个数据表中的数据操作时,必须等这个表锁被释放(再此期间一直等待)。它是MyISAM的默认锁,因为MyISAM不支持事务机制和行锁,所以只有进行DML操作就会加表级别写锁,使用Select时就会加表级别读锁。它的并发度小,但是性能高,对表锁进行分类,主要有以下几个类别:

  • 读锁:允许并发读取数据,禁止写入,常使用以下进行加锁:

    1
    LOCK TABLES table_name READ;
  • 写锁:完全排他,禁止任何读写,使用以下方式进行加锁:

    1
    LOCK TABLES table_name WRITE;
  • 意向锁:虽然也是表级别的锁,但它并不属于传统意义上的“表锁”类别。它是 InnoDB 存储引擎内部用于协调行锁与表锁的元数据锁,用户无法显式申请,也不会阻塞普通的数据读写操作。

页锁

页锁(Page-Level Locking) 是一个理论上存在、但实际在主流引擎中几乎不使用的锁粒度,一个“页”通常是 4KB、8KB 或 16KB 的连续存储块(InnoDB 默认页大小为 16KB)。页锁的粒度介于 表锁(粗)行锁(细) 之间:

  • 比表锁并发性高(只锁一页,不是整表);
  • 比行锁并发性低(一页可能包含多行,会锁住无关数据)。

页锁也是SQL Server默认支持的锁

行锁

行锁是Mysql中最细类别的锁,它只对数据表中某一行进行加锁,所以行锁的并发度高,但是一个表中可以存在多个行锁,所以行锁的性能比较低。它是InnoDB默认支持的锁级别,同时也是仅仅由InnoDB存储引擎支持的锁级别。同时,行锁也会有死锁的风险。行锁可以分成以下类别:

  • 记录锁:锁定索引上的某一条记录,示例:

    1
    2
    -- 假设 id 是主键
    SELECT * FROM users WHERE id = 10 FOR UPDATE;
  • 间隙锁:锁定索引记录之间的“间隙”,防止其他事务在该范围内插入新记录。(用于解决:防止幻读)示例:

    1
    2
    -- 假设 age 有二级索引,当前值有 10, 20, 30
    SELECT * FROM users WHERE age > 15 AND age < 25 FOR UPDATE;
  • 临键锁(Next-Key Lock)):记录锁 + 间隙锁 的组合。InnoDB 在 REPEATABLE READ 隔离级别下的默认行锁类型。锁定“左开右闭”区间,例如 (10, 20]
  • 插入意向锁(Insert Intention Lock):一种特殊的 间隙锁,表示“打算在某个间隙插入数据”。多个事务可在同一间隙的不同位置插入,彼此不阻塞。

行锁依赖索引!若 SQL 无法使用索引,InnoDB 可能退化为全表扫描 + 每行加锁,效果等同于表锁。

这样设计是为了在高并发下,既保证数据正确(一致),又尽量让多人同时操作(并发)

2.按锁的性质分类

排他锁(X锁)

写锁,阻塞其他事务对数据的修改(不允许其他事务获取写锁)

共享锁(S锁)

读锁,允许多个事务同时读取数据,禁止多个事务进行修改数据。

意向锁

用于快速判断是否有事务四修改某个数据,其分类为:

  • 意向共享锁(IS):表示事务打算对表中的某些行加 S 锁(共享锁),与表级 S 锁兼容,与 X 锁互斥。
  • 意向排他锁(IX):表示事务打算对表中的某些行加 X 锁(排他锁),与表级 S/X 锁都可能冲突

意向锁的本质:是一种“快速通道”,让表级锁能高效判断“表中是否有行被锁定”。

3.按锁的机制分类

间隙锁

锁定索引记录间隙,防止幻读。

临键锁

行锁加间隙锁的组合。

自增锁

自增锁是 InnoDB 在执行 INSERT 语句涉及 AUTO_INCREMENT时,自动加的一种轻量级表级锁,用于:

  • 确保多个并发事务插入时,自增值不重复
  • 尽可能保证自增值单调递增(但不一定连续)。

InnoDB 根据 INSERT 语句的形式,将自增分配分为三类,决定是否使用传统自增锁

插入类型 示例 是否使用表级 AUTO-INC 锁? 说明
Simple inserts (简单插入) INSERT INTO t VALUES (NULL, 'A'), (NULL, 'B') INSERT ... VALUES(行数确定) 不使用(MySQL 8.0+ 默认) 可提前计算所需自增值数量,批量分配
Bulk inserts (批量插入) INSERT INTO t SELECT ... LOAD DATA INFILE 使用(短时间持锁) 无法预知插入多少行,需逐行分配
Mixed-mode inserts (混合插入) INSERT INTO t VALUES (1, 'A'), (NULL, 'B') 部分指定 ID,部分用 NULL ⚠️ 部分使用 NULL 的部分按需分配

InnoDB 通过 innodb_autoinc_lock_mode 参数控制自增锁策略(默认值因版本而异):

模式值 名称 行为 并发性 安全性
0 Traditional(传统模式) 所有 INSERT 都加表级 AUTO-INC 锁,直到语句结束 ❌ 低 ✅ 最安全(主从一致)
1 Consecutive(连续模式) (MySQL 5.7 默认 简单插入不锁,批量/混合插入锁 ✅ 中 ⚠️ 主从可能不一致(SBR 下)
2 Interleaved(交错模式) (MySQL 8.0 默认 完全不使用表级自增锁,所有分配无锁 ✅✅ 高 ⚠️ 自增值不连续,主从需用 RBR
  • MySQL 8.0 + 基于行的复制(RBR) → 用默认 mode=2
  • 老版本或使用基于语句的复制(SBR) → 建议 mode=1