十一,MySQL锁机制详解

一,介绍

MySQL的锁机制是保证数据一致性和并发控制的核心组件,尤其在多用户环境下至关重要。

按锁粒度划分

  1. 全局锁(Global Lock)

    • 作用:锁定整个数据库实例,确保备份时数据一致性。
  2. 表级锁(Table-Level Lock)

    • 特点:锁定整张表,开销小、加锁快,但并发度低。
  3. 行级锁(Row-Level Lock)

    • 特点:锁定单行或多行,开销大、加锁慢,但并发度高。

二,全局锁

2.1 介绍

全局锁就是对整个数据库实例加锁。

枷锁后整个实例就处于只读状态,后续的增删改操作,事务提交操作,修改表操作等会处于阻塞状态。全局锁一般用在数据库的逻辑备份,对所有表进行锁定,从而获取一致性视图,确保数据的完整性。

2.2 语法

加全局锁

sql 复制代码
flush tables with read lock;

解锁

sql 复制代码
unlock tables;

2.3 数据库备份(mysqldump)

bash 复制代码
mysqldump -u账号 -p密码 -h主机地址 要备份的数据库 > 备份的路径/xxx.sql

mysqldumpmysql提供的一个备份工具,直接在命令行执行即可不需要进入mysql bashxxx.sql文件存储的是SQL语句!

三,表级锁

3.1 介绍

  1. 锁定粒度:锁定整张表,无论操作涉及多少行数据。
  2. 加锁速度:开销小,加锁快(仅需维护表级别的锁状态)。
  3. 并发性能:并发度低,同一时间只能有一个事务对表进行写操作。
  4. 适用存储引擎
    • MyISAM:默认使用表级锁(仅支持表级锁)。
    • InnoDB:支持行级锁,但在某些场景(如无索引的全表扫描)会退化为表级锁。

3.2 表级锁类型

  1. 表共享读锁(S Lock)
    • 允许其他会话读表,但阻塞所有写操作。
    • 显式加锁:LOCK TABLES table_name READ;
  2. 表独占写锁(X Lock)
    • 独占访问,阻塞其他会话的读写操作。
    • 显式加锁:LOCK TABLES table_name WRITE;
  3. 元数据锁(MDL)
    • 隐式锁,保护表结构变更(DDL操作)。
    • 读锁(SHARED_READ)与写锁(EXCLUSIVE)的互斥规则严格。
  4. 意向锁(Intention Lock)
    • 意向共享锁(IS):事务计划对某些行加共享锁(S)。
    • 意向排他锁(IX):事务计划对某些行加排他锁(X)。

3.3 表共享读锁\表独占写锁

3.3.1 表共享读锁(Shared Read Lock, S Lock)

  • 作用 :允许多个事务同时取表数据,但阻塞所有写操作。
  • 加锁方式
    • 显式加锁LOCK TABLES table_name READ;
    • 隐式加锁 (仅MyISAM引擎):执行SELECT时自动加读锁。
  • 兼容性
    • 允许其他事务加读锁(共享读)。
    • 阻塞其他事务加写锁(互斥写)。
  • 释放条件
    • 显式释放:UNLOCK TABLES;
    • 隐式释放(MyISAM):查询结束后自动释放(非事务引擎)。

加锁流程示例(MyISAM引擎)

场景:会话A加表共享读锁,会话B尝试读写。

sql 复制代码
-- 会话A(显式加读锁)
LOCK TABLES users READ;

-- 会话A执行读操作(允许)
SELECT * FROM users WHERE id=1;

-- 会话B尝试读操作(允许)
SELECT * FROM users WHERE id=2;  -- 成功执行

-- 会话B尝试写操作(阻塞)
UPDATE users SET name='Bob' WHERE id=1;  -- 等待会话A释放锁

-- 会话A释放锁
UNLOCK TABLES;

-- 会话B的UPDATE操作继续执行

结果

  • 会话B的读操作(SELECT)不受影响。
  • 会话B的写操作(UPDATE)被阻塞,直到会话A释放锁。

3.3.2 表独占写锁(Exclusive Write Lock, X Lock)

  • 作用:独占整个表,禁止其他事务读写。
  • 加锁方式
    • 显式加锁LOCK TABLES table_name WRITE;
    • 隐式加锁 (仅MyISAM引擎):执行INSERT/UPDATE/DELETE时自动加写锁。
  • 兼容性
    • 阻塞其他事务的所有读写操作(完全独占)。
  • 释放条件
    • 显式释放:UNLOCK TABLES;
    • 隐式释放(MyISAM):写操作完成后自动释放。

加锁流程示例(MyISAM引擎)

场景:会话A加表独占写锁,会话B尝试读写。

sql 复制代码
-- 会话A(显式加写锁)
LOCK TABLES users WRITE;

-- 会话A执行写操作(允许)
UPDATE users SET name='Alice' WHERE id=1;

-- 会话B尝试读操作(阻塞)
SELECT * FROM users WHERE id=2;  -- 等待会话A释放锁

-- 会话B尝试写操作(阻塞)
INSERT INTO users (id, name) VALUES (3, 'Charlie');  -- 同样被阻塞

-- 会话A释放锁
UNLOCK TABLES;

-- 会话B的SELECT和INSERT操作继续执行

结果:会话B的所有读写操作均被阻塞,直到会话A释放锁。


3.3.3 锁的兼容性与冲突规则

当前锁类型 \ 请求锁类型 共享读锁(S) 独占写锁(X)
共享读锁(S) ✅ 兼容 ❌ 不兼容
独占写锁(X) ❌ 不兼容 ❌ 不兼容
  • 核心规则
    • 读锁之间共享,写锁完全独占。
    • 写锁优先级高于读锁(等待队列中写锁可能优先获取)。

3.3.4 InnoDB中的表级锁退化

InnoDB默认使用行级锁,但在以下场景会退化为表级锁:

  1. 无索引的更新/删除

    sql 复制代码
    -- 若age字段无索引,退化为表锁
    UPDATE users SET status=1 WHERE age > 30;
  2. 显式锁表

    sql 复制代码
    LOCK TABLES users WRITE; -- 强制使用表级锁(破坏InnoDB行锁机制)

示例:InnoDB无索引导致表锁

sql 复制代码
-- 会话A(无索引UPDATE)
BEGIN;
UPDATE users SET status=1 WHERE age > 30;  -- age字段无索引,退化为表锁

-- 会话B尝试更新其他行(被阻塞)
UPDATE users SET name='Dave' WHERE id=5;  -- 因表锁被阻塞,直到会话A提交

3.3.5 监控锁状态

  1. 查看当前锁信息
go 复制代码
 ```sql
sql 复制代码
 SHOW OPEN TABLES WHERE In_use > 0;  -- 查看被锁定的表
 SHOW PROCESSLIST;                  -- 查看阻塞的会话
 ```
  1. MyISAM锁监控
sql 复制代码
 ```sql
 SHOW STATUS LIKE 'Table_locks%';
 -- Table_locks_immediate: 立即获取锁的次数
 -- Table_locks_waited:   需要等待锁的次数(值高表示锁竞争严重)
 ```
  1. InnoDB锁监控(需启用InnoDB监控)
sql 复制代码
 ```sql
 -- 查询INFORMATION_SCHEMA表
 SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS;      -- 当前持有的锁
 SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS; -- 锁等待关系
 ```

3.3.6 总结

  • 表共享读锁(S):允许多读,禁写,适合读多写少场景。
  • 表独占写锁(X):完全独占,适合批量写入或表结构变更。
  • 注意事项
    • MyISAM依赖表级锁,InnoDB慎用显式锁表。
    • 避免长事务持有锁,减少锁竞争。
    • 合理设计索引,防止InnoDB退化为表锁。

3.4 元数据锁

3.4.1 元数据锁(MDL)的核心作用

  • 保护表结构 :防止在数据操作(DML)过程中,表结构(Schema)被意外修改(如ALTER TABLEDROP TABLE),导致数据不一致。
  • 隐式自动管理:由MySQL自动加锁,无需用户手动干预。
  • 所有存储引擎生效:无论是InnoDB、MyISAM还是其他引擎,均受MDL约束。

3.4.2 MDL锁的类型与行为

锁类型 触发场景 兼容性规则
MDL读锁(SHARED_READ) SELECTUPDATEDELETE等DML操作 允许多个会话同时持有,但阻塞DDL操作。
MDL写锁(EXCLUSIVE) ALTER TABLEDROP TABLE等DDL操作 独占锁,阻塞所有其他会话的DML和DDL操作

3.4.3 MDL锁的典型问题场景

  1. 长事务阻塞DDL操作

    sql 复制代码
    -- 会话A(长事务)
    BEGIN;
    SELECT * FROM users; -- 隐式加MDL读锁
    -- 不提交事务...
    
    -- 会话B(DDL操作)
    ALTER TABLE users ADD COLUMN age INT; -- 需要MDL写锁,被阻塞
  • 结果 :会话B的ALTER TABLE会一直等待,直到会话A提交或超时(默认50秒)。
  1. DDL操作阻塞后续查询

    sql 复制代码
    -- 会话A(DDL操作)
    ALTER TABLE users ADD COLUMN email VARCHAR(255); -- 加MDL写锁
    
    -- 会话B(普通查询)
    SELECT * FROM users; -- 需要MDL读锁,被阻塞
  • 结果 :会话B的SELECT在DDL完成前无法执行。

3.4.4 总结

  • MDL锁是隐式锁:自动保护表结构,避免DDL与DML并发导致数据不一致。
  • 核心问题:长事务和DDL操作的冲突是MDL锁问题的根源。
  • 优化关键
    • 及时提交事务,避免长查询。
    • 使用在线DDL工具减少锁冲突。
    • 监控锁等待和会话状态。

3.5 意向锁

3.5.1 意向锁的核心作用

意向锁是 表级锁 ,用于协调 行级锁表级锁 的共存,解决以下问题:

  1. 避免全表扫描检查行锁: 当需要加表级锁时,无需逐行检查是否有行锁冲突,只需检查意向锁即可快速判断。
  2. 支持多粒度锁共存: 允许行级锁(细粒度)和表级锁(粗粒度)同时存在,提升并发效率。
  3. 减少锁冲突概率: 通过层级锁机制(意向锁 -> 行锁),降低锁竞争复杂度。

3.5.2 意向锁的类型

锁类型 缩写 行为
意向共享锁 IS 表示事务计划对表中的某些行加 共享锁(S Lock)(读操作)。
意向排他锁 IX 表示事务计划对表中的某些行加 排他锁(X Lock)(写操作)

3.5.3 意向锁的兼容性规则

意向锁之间的兼容性决定了多个事务能否同时操作同一张表:

当前锁类型 \ 请求锁类型 IS IX 表级S锁(读锁) 表级X锁(写锁)
IS
IX
表级S锁(S)
表级X锁(X)
  • 关键规则
    • IS与IS、IX、表级S锁兼容:允许多个事务同时读取表的不同行。
    • IX与IS、IX兼容:允许多个事务同时修改不同行。
    • 表级S锁/X锁与意向锁互斥:表级锁需要独占访问,必须等待意向锁释放。

3.5.4 加锁流程与示例

1. 加锁规则

  • 层级加锁 :事务在加行锁前,必须先对表加对应的意向锁(IS或IX)。
    • 加行级S锁前 → 先加IS锁。
    • 加行级X锁前 → 先加IX锁。

2. 示例场景

  • 场景1:事务A读取某一行(加IS锁)

    sql 复制代码
    -- 事务A
    BEGIN;
    SELECT * FROM users WHERE id=1 LOCK IN SHARE MODE; -- 对表加IS锁,对id=1加行级S锁
    
    -- 其他事务:
    -- 可以继续对表加IS锁(如SELECT其他行),但无法加表级X锁。
  • 场景2:事务B修改某一行(加IX锁)

    sql 复制代码
    -- 事务B
    BEGIN;
    UPDATE users SET name='Alice' WHERE id=2; -- 对表加IX锁,对id=2加行级X锁
    
    -- 其他事务:
    -- 可以加IS锁(读其他行),但无法加表级S锁或X锁。
  • 场景3:事务C尝试加表级S锁

    sql 复制代码
    -- 事务C
    LOCK TABLES users READ; -- 请求表级S锁
    
    -- 若存在活跃的IX锁(如事务B未提交),事务C会被阻塞,直到所有IX锁释放。

3.5.5 意向锁的意义与优势

  1. 性能优化: 避免在加表级锁时遍历所有行检查锁状态,减少资源消耗。
  2. 锁粒度协调 : 允许行级锁和表级锁共存,例如:
    • 一个事务持有行级X锁(IX锁),另一个事务可持有表级S锁(只要没有IX冲突)。
  3. 死锁预防: 层级锁机制(意向锁先行)减少了循环等待的可能性。

3.5.6 意向锁的监控

通过InnoDB系统表查看意向锁状态:

sql 复制代码
-- 查看当前持有的锁(包括意向锁)
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS;

-- 查看锁等待关系
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS;

3.5.7 总结

  • 意向锁是InnoDB实现多粒度锁的核心机制,平衡了并发性能与数据一致性。
  • IS锁:预示事务要读取某些行。
  • IX锁:预示事务要修改某些行。
  • 核心价值:通过层级锁机制,高效管理行锁与表锁的共存,避免全表扫描检查锁冲突。

四,行级锁

4.1 介绍

行级锁是InnoDB存储引擎的核心特性,用于在 高并发事务 中实现细粒度的数据访问控制,解决以下问题:

  1. 提升并发性能:仅锁定需要操作的行,而非整张表。
  2. 避免锁冲突:不同事务可以同时操作不同行,减少阻塞。
  3. 支持复杂事务 :结合事务隔离级别(如REPEATABLE READ),实现ACID特性。

4.2 记录锁

4.2.1 记录锁的定义与作用

  • 定义 :记录锁是 行级锁 的一种,锁定索引中的 单条记录
  • 核心作用
    1. 确保事务对单行数据的 独占访问共享访问
    2. 防止其他事务并发修改同一行数据,保证数据一致性。

4.2.2 记录锁的触发场景

  1. 显式加锁

    通过SELECT ... FOR UPDATE(排他锁)或SELECT ... LOCK IN SHARE MODE(共享锁)显式加锁。

    sql 复制代码
    -- 排他锁(X Lock)
    BEGIN;
    SELECT * FROM users WHERE id=1 FOR UPDATE; -- 对id=1加记录X锁
    
    -- 共享锁(S Lock)
    BEGIN;
    SELECT * FROM users WHERE id=1 LOCK IN SHARE MODE; -- 对id=1加记录S锁
  2. 隐式加锁

    DML操作UPDATEDELETE语句自动对符合条件的行加 排他锁

    sql 复制代码
    BEGIN;
    UPDATE users SET name='Alice' WHERE id=1; -- 自动对id=1加记录X锁

4.2.3 记录锁的兼容性规则

当前锁类型 \ 请求锁类型 共享锁(S) 排他锁(X)
共享锁(S) ✅ 兼容 ❌ 不兼容
排他锁(X) ❌ 不兼容 ❌ 不兼容
  • 共享锁(S):允许多个事务同时读取同一行。
  • 排他锁(X):独占访问,阻塞其他事务的所有操作。

4.2.4 记录锁的加锁流程示例

  • 场景1:共享锁(S)与共享锁(S)兼容

    sql 复制代码
    -- 事务A
    BEGIN;
    SELECT * FROM users WHERE id=1 LOCK IN SHARE MODE; -- 加S锁
    
    -- 事务B
    BEGIN;
    SELECT * FROM users WHERE id=1 LOCK IN SHARE MODE; -- 允许加S锁,正常读取
  • 场景2:共享锁(S)与排他锁(X)冲突

    sql 复制代码
    -- 事务A
    BEGIN;
    SELECT * FROM users WHERE id=1 LOCK IN SHARE MODE; -- 加S锁
    
    -- 事务B
    BEGIN;
    UPDATE users SET name='Bob' WHERE id=1; -- 尝试加X锁,被阻塞,直到事务A提交
  • 场景3:排他锁(X)完全独占

    sql 复制代码
    -- 事务A
    BEGIN;
    UPDATE users SET name='Alice' WHERE id=1; -- 自动加X锁
    
    -- 事务B
    BEGIN;
    SELECT * FROM users WHERE id=1 FOR UPDATE; -- 尝试加X锁,被阻塞

4.2.5 记录锁的底层实现

  • 依赖索引 :记录锁基于索引 实现,若查询条件无索引,InnoDB会退化为 表级锁
  • 锁定索引记录
    • 若表有主键,直接锁定主键索引。
    • 若表无主键但有唯一索引,锁定唯一索引。
    • 若表无索引,锁定隐藏的DB_ROW_ID(退化为表锁)。

示例:无索引导致表锁

sql 复制代码
-- 假设age字段无索引
BEGIN;
UPDATE users SET status=1 WHERE age=30; -- 无索引,退化为表级锁

4.2.6 记录锁的监控与诊断

  1. 查看当前记录锁

    sql 复制代码
    -- 查看InnoDB锁信息(MySQL 5.7+)
    SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS 
    WHERE LOCK_TYPE = 'RECORD';
    
    -- 输出示例:
    -- LOCK_ID: 1234:5:3:2
    -- LOCK_TRX_ID: 1234       -- 事务ID
    -- LOCK_MODE: X,REC_NOT_GAP -- 排他记录锁(非间隙)
    -- LOCK_DATA: 1            -- 锁定的记录值(如id=1)
  2. 分析锁等待

    sql 复制代码
    -- 查看锁等待关系
    SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS;
    
    -- 输出示例:
    -- requesting_trx_id: 5678  -- 请求锁的事务ID
    -- blocking_trx_id: 1234    -- 持有锁的事务ID
  3. 死锁日志分析

    sql 复制代码
    SHOW ENGINE INNODB STATUS; -- 查看最近死锁信息

在输出日志的LATEST DETECTED DEADLOCK部分,可找到死锁涉及的记录锁详情。

4.2.7 总结

  • 记录锁是行级锁的基石:直接控制单行数据的并发访问。
  • 显式与隐式加锁 :通过FOR UPDATE或DML操作触发,需注意索引设计。
  • 优化核心:索引、事务控制、死锁处理。

4.3 间隙锁

4.3.1 间隙锁的定义与作用

  • 定义 :间隙锁是 行级锁 的一种,锁定索引记录之间的 间隙 (即一个范围,不包含记录本身)。
  • 核心作用
    1. 防止幻读(Phantom Read):确保事务执行过程中,其他事务无法在锁定的间隙内插入新数据。
    2. 范围保护:锁定查询条件覆盖的区间,保证事务多次读取的范围结果一致。
  • 生效条件 :仅在 REPEATABLE READ 隔离级别下生效(InnoDB默认级别)。

4.3.2 间隙锁的触发场景

  1. 范围查询

    sql 复制代码
    -- 锁定id在(5, 10)之间的间隙(不包含5和10)
    SELECT * FROM users WHERE id BETWEEN 5 AND 10 FOR UPDATE;
  2. 非唯一索引的等值查询

    sql 复制代码
    -- 假设age是非唯一索引
    SELECT * FROM users WHERE age = 25 FOR UPDATE; -- 锁定age=25前后的间隙
  3. 唯一索引的间隙查询

    sql 复制代码
    -- 若表中没有id=7的记录,则锁定id=5到id=10的间隙
    SELECT * FROM users WHERE id=7 FOR UPDATE;

4.3.3 间隙锁的兼容性规则

当前锁类型 \ 请求锁类型 共享间隙锁(S) 排他间隙锁(X) 插入意向锁
共享间隙锁(S) ✅ 兼容 ✅ 兼容 ❌ 不兼容
排他间隙锁(X) ✅ 兼容 ✅ 兼容 ❌ 不兼容
插入意向锁 ❌ 不兼容 ❌ 不兼容 ✅ 兼容
  • 关键规则
    • 间隙锁之间完全兼容,允许多个事务同时锁定同一间隙。
    • 间隙锁与插入意向锁互斥,插入操作会被阻塞。

4.3.4 间隙锁的底层实现

  • 基于索引 :间隙锁仅作用于索引 ,若表无索引,InnoDB会创建隐藏的聚簇索引DB_ROW_ID)来实现。
  • 锁定范围
    • 对于唯一索引的等值查询(命中记录),退化为记录锁(无间隙锁)。
    • 对于非唯一索引或未命中记录的查询,锁定间隙。

示例1:非唯一索引触发间隙锁

sql 复制代码
-- 表结构:id(主键), age(非唯一索引)
-- 数据:id=1(age=20), id=3(age=25), id=5(age=30)

-- 事务A
BEGIN;
SELECT * FROM users WHERE age=25 FOR UPDATE; 
-- 锁定age=25的间隙:(20,25)和(25,30)

示例2:唯一索引未命中记录触发间隙锁

sql 复制代码
-- 表数据:id=5, 10, 15(主键)

-- 事务A
BEGIN;
SELECT * FROM users WHERE id=7 FOR UPDATE; 
-- 锁定间隙:(5,10)

4.3.5 间隙锁与幻读的解决

  • 幻读现象:事务A两次读取同一范围,期间事务B插入新数据,导致事务A第二次读取出现"幻行"。

  • 间隙锁的作用

    sql 复制代码
    -- 事务A(REPEATABLE READ隔离级别)
    BEGIN;
    SELECT * FROM users WHERE age > 20 FOR UPDATE; -- 锁定age>20的间隙
    
    -- 事务B
    INSERT INTO users (age) VALUES (25); -- 被阻塞,直到事务A提交
    • 事务A的间隙锁阻止了事务B的插入,确保两次查询结果一致。

4.3.6 间隙锁的监控与诊断

  1. 查看当前间隙锁

    sql 复制代码
    -- 查看InnoDB锁信息(MySQL 5.7+)
    SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS 
    WHERE LOCK_MODE LIKE '%GAP%';
    
    -- 输出示例:
    -- LOCK_ID: 1234:5:3:2
    -- LOCK_TYPE: RECORD
    -- LOCK_MODE: X,GAP       -- 排他间隙锁
    -- LOCK_DATA: 10          -- 锁定的间隙上限(如id=10)
  2. 分析锁等待

    sql 复制代码
    -- 查看被阻塞的插入操作
    SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS;
  3. 死锁日志分析

    sql 复制代码
    SHOW ENGINE INNODB STATUS;

    在日志的LATEST DETECTED DEADLOCK部分,可找到间隙锁冲突导致的死锁信息。

4.3.7 总结

  • 间隙锁是防止幻读的核心机制:通过锁定索引间隙,确保事务范围内的数据一致性。
  • 触发条件:范围查询、非唯一索引查询、唯一索引未命中记录。
  • 优化核心:索引设计、事务隔离级别选择、避免长事务。

4.3 临间锁

4.3.1 临键锁的定义与作用

  • 定义 :临键锁是 行级锁 的一种,由 记录锁(Record Lock)间隙锁(Gap Lock) 组合而成,锁定索引记录及其 之前的间隙
  • 核心作用
    1. 解决幻读(Phantom Read):确保事务执行过程中,其他事务无法在锁定范围内插入新数据。
    2. 范围保护:锁定查询条件覆盖的记录及间隙,保证事务多次读取的范围结果一致。
  • 默认机制 :InnoDB在 REPEATABLE READ 隔离级别下默认使用临键锁。

4.3.2 临键锁的触发场景

  1. 范围查询

    sql 复制代码
    -- 锁定id >= 10的记录及之前的间隙
    SELECT * FROM users WHERE id >= 10 FOR UPDATE;
  2. 非唯一索引的等值查询

    sql 复制代码
    -- 假设age是非唯一索引,锁定age=25的记录及前后间隙
    SELECT * FROM users WHERE age = 25 FOR UPDATE;
  3. 未命中记录的查询

    sql 复制代码
    -- 若表中无id=7的记录,锁定(5,10)的间隙及记录(临键锁)
    SELECT * FROM users WHERE id=7 FOR UPDATE;

4.3.3 临键锁的组成与范围

  • 组成
    • 记录锁:锁定索引中的一条记录(如果存在)。
    • 间隙锁:锁定该记录之前的间隙。
  • 锁定范围
    • 若查询条件命中记录:锁定该记录及其之前的间隙。
    • 若未命中记录:仅锁定间隙(退化为间隙锁)。

示例

sql 复制代码
-- 表数据:id=5, 10, 15(主键)
-- 事务A
BEGIN;
SELECT * FROM users WHERE id BETWEEN 10 AND 20 FOR UPDATE;
  • 锁定范围
    • 记录锁:id=10、15(若存在)。
    • 间隙锁:(10, 15)、(15, 20)。
  • 总锁定区间[10, +∞)

4.3.4 临键锁的兼容性规则

当前锁类型 \ 请求锁类型 临键锁(X) 共享锁(S) 插入意向锁
临键锁(X) ❌ 不兼容 ❌ 不兼容 ❌ 不兼容
共享锁(S) ❌ 不兼容 ✅ 兼容 ✅ 兼容
插入意向锁 ❌ 不兼容 ✅ 兼容 ✅ 兼容
  • 关键规则
    • 临键锁(X)与其他锁完全互斥,保证独占性。
    • 插入意向锁与临键锁互斥,插入操作需等待。

4.3.5 临键锁与幻读的解决

  • 幻读现象:事务A两次读取同一范围,事务B插入新数据,导致事务A第二次读取出现"幻行"。

  • 临键锁的作用

    sql 复制代码
    -- 事务A(REPEATABLE READ隔离级别)
    BEGIN;
    SELECT * FROM users WHERE id > 10 FOR UPDATE; -- 加临键锁,锁定(10, +∞)
    
    -- 事务B
    INSERT INTO users (id) VALUES (12); -- 被阻塞,直到事务A提交
    • 事务A的临键锁阻止了事务B的插入,确保两次查询结果一致。

4.3.6 临键锁的监控与诊断

  1. 查看当前临键锁

    sql 复制代码
    -- 查看InnoDB锁信息(MySQL 5.7+)
    SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS 
    WHERE LOCK_MODE LIKE 'X%'; -- 临键锁模式为X或IX
    
    -- 输出示例:
    -- LOCK_ID: 1234:5:3:2
    -- LOCK_TYPE: RECORD
    -- LOCK_MODE: X           -- 临键锁(默认模式)
    -- LOCK_DATA: 10          -- 锁定的记录值
  2. 分析锁等待

    sql 复制代码
    -- 查看被临键锁阻塞的操作
    SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS;
  3. 死锁日志分析

    sql 复制代码
    SHOW ENGINE INNODB STATUS;

    在日志LATEST DETECTED DEADLOCK`部分,可找到临键锁导致的死锁信息。

4.3.7 临键锁的示例场景

  • 场景1:范围查询触发临键锁

    sql 复制代码
    -- 表数据:id=5, 10, 15
    -- 事务A
    BEGIN;
    SELECT * FROM users WHERE id > 10 FOR UPDATE; -- 锁定(10, 15), (15, +∞)
    
    -- 事务B
    INSERT INTO users (id) VALUES (12); -- 被阻塞
    UPDATE users SET name='Bob' WHERE id=15; -- 被阻塞
  • 场景2:非唯一索引触发临键锁

    sql 复制代码
    -- 表结构:id(主键), age(非唯一索引)
    -- 数据:id=1(age=20), id=3(age=25), id=5(age=30)
    -- 事务A
    BEGIN;
    SELECT * FROM users WHERE age >= 25 FOR UPDATE; 
    -- 锁定age=25的记录及(25,30)的间隙
    
    -- 事务B
    INSERT INTO users (age) VALUES (28); -- 被阻塞

4.3.8 总结

  • 临键锁是InnoDB的默认锁机制 :在REPEATABLE READ级别下,通过组合记录锁和间隙锁彻底解决幻读。
  • 锁定范围:索引记录及其之前的间隙,确保事务范围内的数据一致性。
  • 优化核心:索引设计、查询条件控制、事务拆分。

4.4 插入意向锁

4.4.1 插入意向锁的定义与作用

  • 定义 :插入意向锁是 间隙锁(Gap Lock) 的一种特殊类型,在插入新数据前对目标间隙加锁,表示"准备在此间隙插入数据"。
  • 核心作用
    1. 优化并发插入:允许多个事务在同一间隙的不同位置插入数据(只要不冲突),提高插入效率。
    2. 避免锁冲突:与普通间隙锁不同,插入意向锁之间互相兼容,仅在目标位置冲突时阻塞。

4.4.2 插入意向锁的触发场景

  1. 插入操作前隐式加锁

    sql 复制代码
    -- 事务A尝试插入数据
    BEGIN;
    INSERT INTO users (id) VALUES (15); -- 对间隙(10,20)加插入意向锁
  2. 与间隙锁的冲突

    sql 复制代码
    -- 事务A已锁定间隙(10,20)
    BEGIN;
    SELECT * FROM users WHERE id BETWEEN 10 AND 20 FOR UPDATE; -- 加间隙锁
    
    -- 事务B尝试插入数据
    BEGIN;
    INSERT INTO users (id) VALUES (15); -- 需获取插入意向锁,但被事务A的间隙锁阻塞

4.4.3 插入意向锁的兼容性规则

当前锁类型 \ 请求锁类型 插入意向锁 间隙锁(Gap Lock) 临键锁(Next-Key Lock)
插入意向锁 ✅ 兼容 ❌ 不兼容 ❌ 不兼容
间隙锁(Gap Lock) ❌ 不兼容 ✅ 兼容 ✅ 兼容
临键锁(Next-Key) ❌ 不兼容 ✅ 兼容 ❌ 不兼容
  • 关键规则
    • 插入意向锁之间兼容:允许多个事务在同一间隙插入不同数据(如id=12和id=15)。
    • 插入意向锁与间隙锁互斥:若间隙已被其他事务加间隙锁,插入操作需等待。

4.4.4 插入意向锁的底层实现

  • 锁定目标:插入意向锁仅锁定目标插入点所在的间隙,而非整个范围。
  • 冲突检测
    • 若插入位置已被其他事务的插入意向锁占用(如唯一键冲突),则触发等待。
    • 若插入位置未被占用,直接插入并释放锁。

示例

sql 复制代码
-- 表数据:id=10, 20(主键)
-- 事务A
BEGIN;
INSERT INTO users (id) VALUES (15); -- 对间隙(10,20)加插入意向锁

-- 事务B
BEGIN;
INSERT INTO users (id) VALUES (18); -- 同样对间隙(10,20)加插入意向锁,允许执行

4.4.5 插入意向锁如何避免死锁

  • 场景:多个事务尝试插入同一间隙的不同位置。
  • 机制
    • 插入意向锁之间兼容,事务不会互相阻塞。
    • 若插入位置相同(如唯一键冲突),后插入的事务会等待。

示例

sql 复制代码
-- 事务A和事务B同时插入不同位置
-- 事务A
BEGIN;
INSERT INTO users (id) VALUES (15); -- 成功

-- 事务B
BEGIN;
INSERT INTO users (id) VALUES (18); -- 成功

-- 结果:无死锁,两者均插入成功。

4.4.6 插入意向锁的监控与诊断

  1. 查看当前插入意向锁

    sql 复制代码
    -- 查看InnoDB锁信息(MySQL 5.7+)
    SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS 
    WHERE LOCK_MODE = 'X,INSERT_INTENTION'; -- 插入意向锁模式
    
    -- 输出示例:
    -- LOCK_ID: 1234:5:3:2
    -- LOCK_TYPE: RECORD
    -- LOCK_MODE: X,INSERT_INTENTION
    -- LOCK_DATA: 15          -- 插入目标值
  2. 分析插入意向锁冲突

    sql 复制代码
    -- 查看被阻塞的插入操作
    SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS;
  3. 死锁日志分析

    sql 复制代码
    SHOW ENGINE INNODB STATUS;

    在日志的LATEST DETECTED DEADLOCK部分,可找到插入意向锁导致的死锁信息。


4.4.7 插入意向锁的示例场景

  • 场景1:插入意向锁与间隙锁冲突

    sql 复制代码
    -- 事务A
    BEGIN;
    SELECT * FROM users WHERE id > 10 FOR UPDATE; -- 加临键锁(锁定(10, +∞))
    
    -- 事务B
    BEGIN;
    INSERT INTO users (id) VALUES (15); -- 需要插入意向锁,被事务A阻塞
  • 场景2:插入意向锁之间的兼容

    sql 复制代码
    -- 事务A
    BEGIN;
    INSERT INTO users (id) VALUES (15); -- 加插入意向锁(间隙(10,20))
    
    -- 事务B
    BEGIN;
    INSERT INTO users (id) VALUES (18); -- 加插入意向锁(同一间隙,兼容)

4.4.8 总结

  • 插入意向锁是间隙锁的优化:允许并发插入同一间隙的不同位置,提升写入性能。
  • 冲突机制:与普通间隙锁互斥,但插入意向锁之间兼容。
  • 优化核心:分散插入热点、减少事务时间、合理设计索引。

五,总结

5.1 全局锁(Global Lock)

  • 锁定粒度:整个数据库实例。
  • 核心作用:确保备份时数据一致性。
  • 实现方式
    • FLUSH TABLES WITH READ LOCK(FTWRL):阻塞所有写操作。
    • InnoDB引擎可使用mysqldump --single-transaction实现无锁热备份。
  • 适用场景
    • 全库逻辑备份(非事务引擎表需手动加锁)。
  • 优缺点
    • 优点:数据一致性高。
    • 缺点:阻塞所有写操作,影响业务可用性。

5.2 表级锁(Table-Level Lock)

  • 锁定粒度:整张表。
  • 核心作用:简单高效地控制表级并发。
  • 类型
    1. 表共享读锁(S Lock):允许多个读操作,阻塞所有写操作。
    2. 表独占写锁(X Lock):完全独占,阻塞所有读写操作。
    3. 元数据锁(MDL):隐式保护表结构变更(DDL操作)。
    4. 意向锁(IS/IX):协调行锁与表锁的共存。
  • 适用场景
    • MyISAM引擎默认使用(读多写少场景)。
    • InnoDB在无索引的全表操作时退化为表锁。
  • 优缺点
    • 优点:开销小,加锁快,适合低并发场景。
    • 缺点:并发性能差,锁冲突概率高。

5.3 行级锁(Row-Level Lock)

  • 锁定粒度:单行或多行数据。
  • 核心作用:实现高并发下的细粒度控制。
  • 类型
    1. 记录锁(Record Lock):锁定单行。
    2. 间隙锁(Gap Lock):锁定索引记录间的间隙。
    3. 临键锁(Next-Key Lock):记录锁 + 间隙锁(InnoDB默认)。
    4. 插入意向锁(Insert Intention Lock):优化并发插入。
  • 适用场景
    • InnoDB引擎默认支持,适合高并发写入。
    • 需精确控制数据访问的事务场景。
  • 优缺点
    • 优点:并发度高,锁冲突少。
    • 缺点:开销大,可能引发死锁。
相关推荐
伊H几秒前
MYSQL初阶(暂为自用草稿)
数据库·mysql
青春:一叶知秋14 分钟前
【MySQL数据库】数据类型
linux·服务器·mysql
GreatSQL社区2 小时前
GreatSQL启动崩溃:jemalloc依赖缺失问题排查
数据库·mysql
rock3653375 小时前
PHP怎样连接MySQL数据库?
数据库·mysql·php
南客先生6 小时前
海量聊天数据处理:基于Spring Boot与SharingJDBC的分库分表策略及ClickHouse冷热数据分离
mysql·clickhouse·springboot·分库分表·大数据处理·sharingjdbc
柳如烟@9 小时前
基于GTID的主从复制
运维·mysql·gdit
小趴菜吖9 小时前
MySQL数据库表查询
数据库·mysql
一只帆記12 小时前
Oracle、MySQL、PostgreSQL三大数据库对比分析
数据库·mysql·postgresql·oracle
hweiyu0015 小时前
MySQL中常用函数的分类及示例
数据库·mysql