文章中如果有图看不到,可以点这里去 csdn 看看。从那边导过来的,文章太多,没法一篇篇修改好。

MySQL 锁知识点大全源码级分析【共享锁、排他锁、意向锁、记录锁、间隙锁、临键锁、死锁、锁退化】

核心思想

MySQL 的锁机制,尤其是在 InnoDB 存储引擎中,其核心目标是在保证数据一致性(ACID 中的 I-Isolation)的前提下,最大限度地提高数据库的并发性能。它通过多粒度锁定(Multigranularity Locking)和意向锁(Intention Locking)来实现这一目标。


一、 锁的类型与详细用法

MySQL 的锁可以按多个维度进行分类。

1. 按操作类型划分

a. 共享锁 (Shared Lock, S-Lock)

  • 用处:用于读取操作。多个事务可以同时持有对同一资源的共享锁。
  • 用法
    SELECT ... LOCK IN SHARE MODE; -- 老语法,但仍有效
    SELECT ... FOR SHARE; -- MySQL 8.0+ 推荐语法
    
  • 特点
    • 兼容性:与其他的 S-Lock 兼容,与 X-Lock 不兼容。
    • 目的:确保在您读取数据的过程中,数据不会被其他事务修改(但可以被其他事务并发读取)。它提供了一种“读锁”的机制。

b. 排他锁 (Exclusive Lock, X-Lock)

  • 用处:用于写入操作(UPDATE, DELETE, INSERT)。一个事务持有某资源的排他锁后,其他事务无法再获取该资源的任何类型的锁。
  • 用法
    • 自动加锁:UPDATE, DELETE, INSERT 语句会自动为受影响的数据加 X-Lock。
    • 手动加锁:SELECT ... FOR UPDATE;
  • 特点
    • 兼容性:与任何其他锁(S 或 X)都不兼容。
    • 目的:确保在您修改数据的过程中,数据不会被其他事务以任何方式读取(FOR SHARE)或修改(FOR UPDATE),从而避免脏写和脏读。

2. 按锁的粒度划分

这是理解 InnoDB 锁的关键。

a. 行级锁 (Row-Level Lock)

  • 用处:锁定表中的单行或多行记录。这是 InnoDB 实现高并发的最重要特性。
  • 用法:通过 SELECT ... FOR UPDATE 或 DML 语句在满足条件的行上自动获取。
  • 特点
    • 粒度小,并发度高,但开销最大。
    • 行锁实际上是加在索引记录上的。这一点至关重要!
      • 如果查询条件有索引,则锁住对应的索引记录。
      • 如果查询条件无索引或索引失效,会导致 行锁升级为表锁(实际上是锁住所有扫描过的记录和间隙,效果类似表锁,性能极差)。

行级锁的细分类型:

  1. 记录锁 (Record Lock)

    • 用处:锁定索引中的一条具体记录
    • 场景SELECT * FROM t WHERE id = 1 FOR UPDATE; 会在 id=1 的索引记录上加 X 型记录锁。
    • 源码lock_rec_lock 函数,锁对象存储在 lock_sys->rec_hash 哈希表中,通过 (space_id, page_no, heap_no) 可以快速定位到某一行上的锁。
  2. 间隙锁 (Gap Lock)

    • 用处:锁定一个索引记录之间的范围,但不包括记录本身。目的是防止其他事务在这个范围内插入新数据,从而解决幻读问题。
    • 场景SELECT * FROM t WHERE id BETWEEN 5 AND 10 FOR UPDATE; 会在 (5, 10) 这个开区间加 Gap Lock,阻止 id=6,7,8,9 的新记录插入。
    • 特点
      • 只在 READ COMMITTED 及以上隔离级别生效(但 MySQL 在 READ COMMITTED 下又会禁用 Gap Lock)。
      • 仅用于防止插入。
    • 源码:同样是 lock_rec_lock 函数,通过 LOCK_GAP 模式标识。
  3. 临键锁 (Next-Key Lock)

    • 用处记录锁 (Record Lock) + 间隙锁 (Gap Lock) 的组合。锁定一个索引记录及其之前的间隙。这是 InnoDB 在 REPEATABLE READ 隔离级别下默认的行锁算法
    • 场景:如果表中有数据 10, 20, 30,执行 SELECT * FROM t WHERE id = 20 FOR UPDATE; 不仅会锁住 id=20 这条记录,还会锁住 (10, 20] 这个区间。这既防止了其他事务修改 id=20 的记录,也防止了在 1020 之间插入新记录(如 id=15)。
    • 目的:彻底解决幻读问题。
    • 源码:默认的行锁模式,由 LOCK_ORDINARY 标识。
  4. 插入意向锁 (Insert Intention Lock)

    • 用处:一种特殊的间隙锁,由 INSERT 操作在插入行之前申请。它** signaling 其意图,目的是为了优化多个事务在相同间隙内的插入操作**,只要插入的位置不冲突,就不需要互相等待。
    • 特点:互相兼容(如果插入的位置不同)。例如,事务 A 想在间隙中插入 id=15,事务 B 想插入 id=16,它们不会互相阻塞。
    • 源码lock_rec_insert_intention 函数,模式为 LOCK_X | LOCK_GAP | LOCK_INSERT_INTENTION

b. 表级锁 (Table-Level Lock)

  • 用处:锁定整张表。
  • 用法
    • 自动加锁:DDL 语句(如 ALTER TABLE)会自动加表级锁。
    • 手动加锁:LOCK TABLES ... READ/WRITE; (服务器层命令,与事务不同,一般不建议在 InnoDB 中使用)。
  • 特点
    • 粒度大,并发度极低,但开销小。
    • InnoDB 通常不需要手动表锁,因为其行锁粒度更细。LOCK TABLES 会隐式提交当前事务。

c. 意向锁 (Intention Lock)

  • 用处表级锁,用于快速判断表中是否有行被锁定。它是一种“信号灯”锁,表明一个事务即将(或正在)对表中的某些行施加 S-Lock 或 X-Lock。
  • 类型
    • 意向共享锁 (Intention Shared Lock, IS):事务打算给某些行加 S-Lock。
    • 意向排他锁 (Intention Exclusive Lock, IX):事务打算给某些行加 X-Lock。
  • 特点
    • 意向锁之间是兼容的(例如,多个事务可以同时持有 IX 锁,因为它们可能只是修改不同的行)。
    • 意向锁的主要目的是为了在加表锁时(例如,执行 ALTER TABLE)能够快速判断是否可以立即获得表锁,而无需逐行检查是否有行锁冲突。如果表上有 IS/IX,说明有行被锁定,表锁请求可能会被阻塞。

二、 死锁 (Deadlock)

1. 条件与产生场景

死锁是指两个或多个事务在执行过程中,因争夺锁资源而造成的一种互相等待的现象,若无外力干涉,它们都无法继续执行。

经典场景:

  1. 场景一:交叉更新

    • 事务 A:UPDATE t SET ... WHERE id = 1; (持有 id=1 的 X-Lock)
    • 事务 B:UPDATE t SET ... WHERE id = 2; (持有 id=2 的 X-Lock)
    • 事务 A:UPDATE t SET ... WHERE id = 2; (尝试获取 id=2 的 X-Lock,被 B 阻塞)
    • 事务 B:UPDATE t SET ... WHERE id = 1; (尝试获取 id=1 的 X-Lock,被 A 阻塞) -> 死锁形成
  2. 场景二:间隙锁冲突

    • 两个事务在相同的间隙上执行 SELECT ... FOR UPDATEINSERT,也可能因 Next-Key Lock 和 Insert Intention Lock 的交互而产生死锁。

2. 检测与解决

  • 检测:InnoDB 使用等待图 (Wait-for Graph) 算法来主动检测死锁。它维护一个事务等待链表,如果检测到循环(A 等 B,B 等 A),即判定为死锁。
  • 解决
    1. 自动解决:InnoDB 的死锁检测引擎一旦发现死锁,会立即回滚其中代价较小的事务(通常是根据修改的行数来判断)。另一个事务因此可以继续执行。应用程序会收到 1213 错误:Deadlock found when trying to get lock; try restarting transaction
    2. 手动处理:应用程序必须捕获这个死锁错误,并选择重试整个事务。这是处理死锁的标准做法。

3. 避免策略

  1. 保持事务小巧且简短,尽快提交,减少锁的持有时间。
  2. 以固定的顺序访问表和数据行。这是避免死锁最有效的方法。如果所有事务都按 先id=1,后id=2 的顺序操作,上例中的死锁就不会发生。
  3. 在事务中尽量一次锁定所有需要的资源
  4. 为查询建立合适的索引,避免因全表扫描导致锁住大量不需要的数据,增加冲突概率。
  5. 如果死锁不频繁,可以临时关闭死锁检测 (innodb_deadlock_detect = OFF),依赖锁等待超时 (innodb_lock_wait_timeout) 来解锁。但这在高压环境下可能导致长时间等待,需谨慎使用。

三、 锁退化 (Lock Degradation)

锁退化是指将锁的粒度降低的过程,例如将一个保护范围很大的锁(如表锁)替换为多个保护范围更小的锁(如行锁)。

  • InnoDB 中的体现:严格来说,InnoDB 的设计中没有传统意义上的“锁退化”。它遵循的是“锁升级”(Lock Escalation)的反面逻辑。InnoDB 的理念是:始终从最细的粒度(行锁)开始加锁

  • 过程:当一个查询需要扫描大量数据时,InnoDB 不会先加一个表锁,而是会逐行(或逐页)地加行锁。它并不会先持有粗粒度锁再退化为细粒度锁。

  • “类似退化”的行为:可以理解为 InnoDB 在事务执行过程中,根据优化器的判断,动态地调整锁定的范围。例如,在 WHERE 条件复杂时,可能一开始锁定的范围较大,随着过滤条件的应用,实际生效的锁范围可能缩小。但这更多是锁的释放,而非主动的退化机制。

锁退化的过程

事务开始执行数据操作
优化器分析查询条件
查询是否可以使用索引?
初始锁定策略: 保守方案
初始锁定策略: 精确方案
可能先扫描并锁定多个页或大量行
在执行过程中应用WHERE条件过滤
释放不满足条件的行上的锁
完成操作
直接通过索引定位并锁定所需行

这与某些数据库(如 SQL Server)先加意向锁再加行锁,或在行锁过多时升级为表锁的机制有根本区别。InnoDB 的架构决定了它优先使用行锁,且没有锁升级机制。


四、 源码级简要分析

锁系统的核心代码在 /storage/innobase/lock/ 目录下。

  1. 锁对象结构 (lock_t): 代表一个事务对一个资源加的锁。包含事务指针、锁类型、模式、哈希指针等。
  2. 锁系统结构 (lock_sys_t): 全局锁管理器。最重要的是 rec_hash,一个哈希表,用于根据(space_id, page_no) 快速找到页上的所有锁。
  3. 加锁流程
    • 调用入口:lock_rec_lock (行锁) 或 lock_table_lock (表锁)。
    • 冲突检查:通过锁模式兼容性矩阵检查当前请求的锁与现有锁是否兼容。
    • 无冲突:创建 lock_t 对象,加入到对应记录的锁队列中。
    • 有冲突:创建锁对象,但设置为等待状态 (LOCK_WAIT),并启动死锁检测。
  4. 死锁检测 (lock_deadlock_check): 以请求锁的事务为起点,深度优先遍历(DFS)等待图。如果发现环,则触发回滚。

总结表

锁类型英文名粒度主要用途兼容性备注
共享锁 (S)Shared Lock行/表读取,防止被修改与 S 兼容,与 X 不兼容SELECT ... FOR SHARE
排他锁 (X)Exclusive Lock行/表写入,防止任何其他操作与任何锁都不兼容DML 语句默认持有,SELECT ... FOR UPDATE
意向共享锁 (IS)Intention Shared Lock表明事务即将对某些行加 S 锁与 IX, IS, S 兼容信号锁,用于快速判断表锁是否可行
意向排他锁 (IX)Intention Exclusive Lock表明事务即将对某些行加 X 锁与 IS, IX 兼容信号锁,用于快速判断表锁是否可行
记录锁Record Lock锁定索引中的一条具体记录行锁的基础
间隙锁Gap Lock行(间隙)锁定一个区间,防止插入,解决幻读RC 隔离级别下禁用
临键锁Next-Key Lock行(记录+间隙)默认行锁模式,锁记录+间隙,彻底解决幻读RR 隔离级别的默认算法
插入意向锁Insert Intention Lock行(间隙)优化在相同间隙内的并发插入互相兼容(如果位置不冲突)特殊的间隙锁

锁源码详解

1. 共享锁(S锁)与排他锁(X锁)

共享锁(S Lock):

// 源码: storage/innobase/lock/lock0lock.cc
/* 共享锁特性 */
#define LOCK_S_SHARED            1   /* 共享读锁 */
#define LOCK_S_ALLOW_READ          /* 允许其他事务读 */
#define LOCK_S_BLOCK_WRITE         /* 阻塞其他事务写 */

排他锁(X Lock):

// 源码: storage/innobase/lock/lock0lock.cc  
/* 排他锁特性 */
#define LOCK_X_EXCLUSIVE         2   /* 独占写锁 */
#define LOCK_X_BLOCK_READ         /* 阻塞其他事务读 */
#define LOCK_X_BLOCK_WRITE        /* 阻塞其他事务写 */

兼容性矩阵:

当前锁 \ 请求锁S锁X锁
S锁
X锁

2. 意向锁(Intention Locks)

意向锁是表级锁,用于快速判断表中是否有行被锁定:

// 源码: storage/innobase/include/lock0types.h
typedef enum {
    LOCK_IS = 4,    /* 意向共享锁 */
    LOCK_IX = 5,    /* 意向排他锁 */ 
    LOCK_S = 6,     /* 共享锁 */
    LOCK_X = 7,     /* 排他锁 */
    /* ... */
} lock_mode_t;

意向锁兼容矩阵:

当前锁 \ 请求锁ISIXSX
IS
IX
S
X

3. 行级锁的具体实现

(1) 记录锁(Record Lock)

锁定索引中的单条记录:

-- 锁定id=1的记录
SELECT * FROM table WHERE id = 1 FOR UPDATE;

源码实现:

// storage/innobase/lock/lock0lock.cc
lock_rec_lock(...) {
    /* 创建记录锁 */
    lock_rec_add_to_queue(
        LOCK_REC | LOCK_X,  /* 记录排他锁 */
        block, heap_no, index, thr);
}

(2) 间隙锁(Gap Lock)

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

-- 锁定10到20之间的间隙
SELECT * FROM table WHERE id BETWEEN 10 AND 20 FOR UPDATE;

源码实现:

// storage/innobase/lock/lock0lock.cc
lock_rec_lock(...) {
    if (gap_lock_needed) {
        /* 创建间隙锁 */
        lock_rec_add_to_queue(
            LOCK_GAP | LOCK_X,  /* 间隙排他锁 */
            block, heap_no, index, thr);
    }
}

(3) 临键锁(Next-Key Lock)

记录锁 + 间隙锁的组合(默认行锁模式):

// storage/innobase/include/lock0types.h
#define LOCK_ORDINARY         0   /* 临键锁 */

(4) 插入意向锁(Insert Intention Lock)

特殊的间隙锁,优化并发插入:

// storage/innobase/lock/lock0lock.cc
lock_rec_insert_intention(...) {
    /* 创建插入意向锁 */
    lock_rec_add_to_queue(
        LOCK_GAP | LOCK_INSERT_INTENTION,
        block, heap_no, index, thr);
}

锁的内存结构与管理系统

1. 锁对象结构

// storage/innobase/include/lock0priv.h
struct lock_t {
    trx_t* trx;             /* 所属事务 */
    lock_t* hash_next;      /* 哈希链指针 */
    dict_index_t* index;    /* 索引 */
    lock_type_t type;       /* 锁类型 */
    /* ... */
};

// 锁表结构
struct lock_sys_t {
    hash_table_t* rec_hash; /* 行锁哈希表 */
    /* ... */
};

2. 锁等待机制

// storage/innobase/lock/lock0wait.cc
lock_wait_suspend_thread(...) {
    /* 线程挂起等待锁 */
    thd_wait_begin(thd, THD_WAIT_ROW_LOCK);
    os_event_wait(lock->event);
    thd_wait_end(thd);
}

死锁检测与处理

1. 死锁条件

  • 互斥条件:资源独占
  • 请求与保持:持有锁并请求新锁
  • 不剥夺条件:锁只能自愿释放
  • 循环等待:等待环路形成

2. 死锁检测算法

// storage/innobase/lock/lock0lock.cc
lock_deadlock_check(...) {
    /* 使用等待图(Wait-for Graph)检测 */
    if (lock_deadlock_bfs(trx)) {
        /* 发现死锁,选择代价小的事务回滚 */
        trx_rollback_to_savepoint( victim_trx );
    }
}

检测流程

  1. 构建等待图(事务为节点,锁等待为边)
  2. 深度优先搜索检测环路
  3. 选择回滚代价最小的事务

3. 死锁避免策略

-- 设置锁超时
SET innodb_lock_wait_timeout = 50;

-- 按固定顺序访问表
-- 事务1: A → B → C
-- 事务2: A → B → C  (而非 C → B → A)

锁退化机制

1. 定义与原理

锁退化(Lock Degradation)是指从高粒度锁退化为低粒度锁的过程,如从表锁退化为行锁。

源码实现:

// storage/innobase/lock/lock0lock.cc
lock_rec_convert_to_gap(...) {
    /* 将记录锁转换为间隙锁 */
    lock_rec_remove_from_queue(lock);
    lock_rec_add_to_queue(LOCK_GAP, ...);
}

2. 触发条件

  • 精确锁定范围可确定时
  • 索引可用性变化时
  • 统计信息更新后

3. 性能优化意义

  • 减少不必要的锁定范围
  • 提高系统并发性能
  • 降低锁争用和死锁概率

锁监控与诊断

1. 性能模式监控

-- 查看当前锁信息
SELECT * FROM performance_schema.data_locks;
SELECT * FROM performance_schema.data_lock_waits;

-- 查看锁等待链
SELECT * FROM sys.innodb_lock_waits;

2. InnoDB状态监控

SHOW ENGINE INNODB STATUS\G
-- 关注 LATEST DETECTED DEADLOCK 和 TRANSACTIONS 部分

3. 关键性能指标

-- 锁等待统计
SELECT * FROM sys.metrics 
WHERE VARIABLE_NAME LIKE '%lock%wait%';

-- 死锁次数
SELECT * FROM information_schema.INNODB_METRICS 
WHERE NAME = 'lock_deadlocks';

最佳实践与优化建议

1. 索引设计优化

-- 确保查询使用索引,避免全表锁
ALTER TABLE orders ADD INDEX idx_customer (customer_id);

-- 使用覆盖索引减少回表锁
CREATE INDEX idx_covering ON orders (status, created_date) 
INCLUDE (amount, customer_id);

2. 事务设计原则

-- 保持事务短小
START TRANSACTION;
-- 最小化锁持有时间
UPDATE ... WHERE id = 1;
COMMIT;

-- 避免长事务
SET SESSION max_execution_time = 5000;  -- 5秒超时

3. 隔离级别选择

-- 读多写少场景
SET SESSION transaction_isolation = 'READ-COMMITTED';

-- 需要避免幻读
SET SESSION transaction_isolation = 'REPEATABLE-READ';

源码级调优参数

# InnoDB锁相关配置
innodb_lock_wait_timeout=50      # 锁等待超时(秒)
innodb_deadlock_detect=ON        # 死锁检测开关
innodb_print_all_deadlocks=OFF   # 死锁日志打印

# 锁系统内存配置
innodb_buffer_pool_size=8G       # 缓冲池大小
innodb_lru_scan_depth=1024       # LRU扫描深度

MySQL的锁机制是一个复杂而精密的系统,深入理解其原理和实现对于构建高性能、高可用的数据库应用至关重要。通过合理的索引设计、事务控制和系统调优,可以最大限度地发挥MySQL的并发性能优势。

posted @ 2025-08-30 15:09  NeoLshu  阅读(5)  评论(0)    收藏  举报  来源