MySQL-InnoDB锁与事务

InnoDB锁与事务

  • [1. 锁的分类总览📊](#1. 锁的分类总览📊)
  • [2. 按锁模式分类(兼容性维度)🔒](#2. 按锁模式分类(兼容性维度)🔒)
    • [2.1 共享锁(S锁,Shared Lock)](#2.1 共享锁(S锁,Shared Lock))
    • [2.2 排他锁(X锁,Exclusive Lock)](#2.2 排他锁(X锁,Exclusive Lock))
  • [3. 按锁粒度分类(锁定范围维度)📏](#3. 按锁粒度分类(锁定范围维度)📏)
    • [3.1 表级锁(Table-Level Lock)](#3.1 表级锁(Table-Level Lock))
    • [3.2 行级锁(Row-Level Lock)](#3.2 行级锁(Row-Level Lock))
      • [3.2.1 记录锁(Record Lock)](#3.2.1 记录锁(Record Lock))
      • [3.2.2 间隙锁(Gap Lock)](#3.2.2 间隙锁(Gap Lock))
      • [3.2.3 临键锁(Next-Key Lock)](#3.2.3 临键锁(Next-Key Lock))
      • [3.2.4 RC隔离级别与间隙锁和临键锁](#3.2.4 RC隔离级别与间隙锁和临键锁)
      • [3.2.5 插入意向锁(Insert Intention Lock)](#3.2.5 插入意向锁(Insert Intention Lock))
  • [4. 按加锁方式分类🎭](#4. 按加锁方式分类🎭)
    • [4.1 显式锁(Explicit Lock)](#4.1 显式锁(Explicit Lock))
    • [4.2 隐式锁(Implicit Lock)](#4.2 隐式锁(Implicit Lock))
  • [5. 锁的生命周期:获取与释放时机⏱️](#5. 锁的生命周期:获取与释放时机⏱️)
    • [5.1 获取锁的时机](#5.1 获取锁的时机)
    • [5.2 释放锁的时机](#5.2 释放锁的时机)
  • [6. 锁的兼容性与冲突矩阵🔍](#6. 锁的兼容性与冲突矩阵🔍)
  • [7. 如何查看和诊断锁](#7. 如何查看和诊断锁)
    • [7.1 查看当前锁信息](#7.1 查看当前锁信息)
    • [7.2 查看事务信息](#7.2 查看事务信息)
    • [7.3 锁超时设置](#7.3 锁超时设置)
  • [8. 总结💡](#8. 总结💡)

问题INSERT插入操作会加锁吗?


1. 锁的分类总览📊

下面的结构图展示了InnoDB锁的分类体系:
InnoDB锁分类
按锁模式
按锁粒度
按加锁方式
共享锁 S锁
排他锁 X锁
表锁
行锁
记录锁 Record Lock
间隙锁 Gap Lock
临键锁 Next-Key Lock
插入意向锁 Insert Intention Lock
显式锁
隐式锁


2. 按锁模式分类(兼容性维度)🔒


2.1 共享锁(S锁,Shared Lock)

  • 获取方式SELECT ... LOCK IN SHARE MODESELECT ... FOR SHARE
  • 特性
    • 允许多个事务同时获取同一资源的S锁。
    • 持有S锁的事务可以读取数据,但不能修改。
    • S锁与S锁兼容,与X锁不兼容。
  • 使用场景:读取数据时需要防止其他事务修改,但允许其他事务并发读。

2.2 排他锁(X锁,Exclusive Lock)

  • 获取方式SELECT ... FOR UPDATE、UPDATE、DELETE、INSERT
  • 特性
    • 一次只能有一个事务获取资源的X锁
    • 持有X锁的事务可以读取和修改数据。
    • X锁与任何其他锁都不兼容(包括其他X锁和S锁)。
  • 使用场景:需要修改数据时,防止其他事务读写。

兼容性矩阵

当前锁\请求锁 S锁 X锁
S锁 ✅ 兼容 ❌ 不兼容
X锁 ❌ 不兼容 ❌ 不兼容

3. 按锁粒度分类(锁定范围维度)📏


3.1 表级锁(Table-Level Lock)

  • 锁定范围:整张表。
  • 类型
    • 表级S锁LOCK TABLES table_name READ
    • 表级X锁LOCK TABLES table_name WRITE
    • 意向锁(Intention Lock) :表级锁,表示事务稍后将在表中的 某些行 上加S锁或X锁。
      • 意向共享锁(IS) :英文名:Intention Shared Lock,简称IS锁。当我们对使用InnoDB存储引擎的表中的某些记录加S锁之前,那就需要先在表级别加一个IS锁。SELECT ... LOCK IN SHARE MODE 前自动获取。
      • 意向排他锁(IX) :英文名:Intention Exclusive Lock,简称IX锁。当我们对使用InnoDB存储引擎的表中的某些记录加X锁之前,那就需要先在表级别加一个IX锁。SELECT ... FOR UPDATE 前自动获取。
      • IS锁和IX锁的使命 :只是为了后续 在加表级别的S锁和X锁时 判断 表中是否有已经被加锁的 记录,以避免用遍历的方式来查看表中有没有上锁的记录。
  • 特点:实现简单,但并发度低。InnoDB通常使用行锁,但在特定情况下(如DDL操作)会使用表锁。

3.2 行级锁(Row-Level Lock)

这是InnoDB【高并发】的核心,细分为:

3.2.1 记录锁(Record Lock)

  • 锁定对象索引记录 (注意:锁的是【索引】,不是数据行本身)。

  • 锁定范围:单个索引记录。

  • 示例

    sql 复制代码
    -- 锁定 id = 10 的记录(假设id是主键或唯一索引)
    SELECT * FROM cus_info WHERE id = 10 FOR UPDATE; -- 排它锁(X锁)
    
    -- update
    -- 给 id为10的索引加上排它锁。
    UPDATE cus_info SET name='盖世无双' WHERE id = 10; 
    -- 给 id为 10 和 20 的【两条索引】都加上排它锁(X锁)。
    UPDATE cus_info SET name='盖世无双' WHERE id IN (10, 20); 
    
    -- delete、insert 同样
  • 特点 :最基本的行锁,只锁定一条记录(这是针对间隙锁的说法)。更准确的说法 :记录锁(Record Lock)是InnoDB中锁定粒度最小的锁,一个记录锁(Record Lock)的锁对象是索引中的一条记录(Index Record),但一条SQL语句可能通过一次加锁操作,给符合条件的多条索引记录都加上**【各自的】**记录锁。

sql 复制代码
	-- 表结构
	CREATE TABLE user (
		id INT PRIMARY KEY,   -- 主键索引
		age INT,
		INDEX idx_age (age)   -- 普通索引
	);
	
	-- 数据
	INSERT INTO user VALUES (1, 20), (2, 20), (3, 25);
	
	-- 事务1
	START TRANSACTION;
	SELECT * FROM user WHERE age = 20 FOR UPDATE; -- 应该锁定 id = 1, 2
	
	-- 事务2:测试是否真的锁定了多行
	START TRANSACTION;
	-- 尝试更新被锁定的行(应该等待)
	UPDATE user SET age = 21 WHERE id = 1; -- 等待
	UPDATE user SET age = 21 WHERE id = 2; -- 等待
	-- 尝试更新未被锁定的行(应该成功)
	UPDATE user SET age = 31 WHERE id = 3; -- 成功

3.2.2 间隙锁(Gap Lock)

  • 锁定对象索引记录之间的间隙,不包括记录本身。

  • 锁定范围:一个开区间范围,如(5, 10)。

  • 示例

    sql 复制代码
    -- 锁定id在5到10之间的间隙,防止其他事务插入id=6,7,8,9的记录
    SELECT * FROM cus_info WHERE id > 5 AND id < 10 FOR UPDATE;
  • 特点

    • gap锁 只在 REPEATABLE READ(可重复读)及以上隔离级别生效
    • 主要用于 防止幻读(Phantom Read),对于MySQL的Innodb存储引擎来说,MVCC(Multi-Version Concurrency Control,多版本并发控制)通过 ReadView 和 undo log版本链 来解决 不可重复读和幻读。
    • 间隙锁之间不冲突(两个事务可以锁同一个间隙)。

3.2.3 临键锁(Next-Key Lock)

  • 锁定对象记录锁 + 间隙锁 的组合。

  • 锁定范围:左开右闭区间,如(5, 10]。

  • 示例

    sql 复制代码
    -- 假设有记录id=5,10,15
    -- 锁定范围(5,10],包括间隙(5,10) 和 记录10
    SELECT * FROM cus_info WHERE id > 5 AND id <= 10 FOR UPDATE;
  • 特点

    • 临键锁(Next-Key Lock)是 InnoDB 在 REPEATABLE READ(可重复读) 下的 默认行锁算法
    • 既防止幻读,又锁定现有记录。

3.2.4 RC隔离级别与间隙锁和临键锁

在 RC(READ COMMITTED,读已提交)隔离级别下,InnoDB 只使用 记录锁(Record Lock),【不使用】间隙锁(Gap Lock)或 临键锁(Next-Key Lock)。这是它与 RR(REPEATABLE READ,可重复读)最根本的区别之一。

对于下面的Sql语句

sql 复制代码
SELECT * FROM cus_info WHERE id > 2 AND id < 7 FOR UPDATE;

加锁过程如下:

  1. 执行查询 :找到所有满足 id > 2 AND id < 7 【已存在】的记录
  2. 逐条加锁 :对 找到的【每一条记录】 ,在对应的 索引条目上加 排他记录锁(X型记录锁)
  3. 不加间隙锁 :不会对 (2, 7) 这个区间范围本身加锁。

具体示例

假设表中数据为:id = 1, 2, 3, 5, 7, 8

操作步骤 效果
1. 执行查询 找到 id = 3id = 5 两条记录
2. 加记录锁 id = 3id = 5 分别加 X型记录锁
3. 完成 锁定了这两条现有记录,结束

关键结论

  • 在RC级别下,这条语句 只锁定了 id = 3id = 5(假设只有这两条记录在范围内)。
  • 不会阻止 其他事务插入 id = 4id = 6 的新记录。
  • 这符合RC的语义:防止读取时数据被修改,但不防止幻读。
sql 复制代码
	-- 事务1(RC隔离级别)
	SET transaction_isolation = 'READ-COMMITTED';
	START TRANSACTION;
	SELECT * FROM cus_info WHERE id > 2 AND id < 7 FOR UPDATE; -- 假设找到 id = 3, 5
	
	-- 事务2(RC隔离级别)
	SET transaction_isolation = 'READ-COMMITTED';
	START TRANSACTION;
	INSERT INTO cus_info (id, name) VALUES (4, '新记录'); -- 应该成功,RC下无间隙锁。
	UPDATE cus_info SET name = '冲突' WHERE id = 3; -- 应该阻塞,id = 3 已被锁定。

	-- 可以通过下面两张表查看 MySQL中的锁信息。
	-- 注意:查询条件 可以带上 库名和表名。
	SELECT * FROM performance_schema.data_locks WHERE object_schema = 'test' and object_name = 'cus_info';
	SELECT * FROM performance_schema.data_lock_waits;

对于 SELECT * FROM cus_info WHERE id > 2 AND id < 7 FOR UPDATE; 语句看看 如下两种事务隔离级别下的锁信息

  • 如果事务隔离级别是RC(READ-COMMITTED,读已提交)的话,事务1执行完的锁信息如下:
    • ① 给表加了IX(意向排它锁)。
    • ② 从 lock_data列 可以看出来,给 id = 3 和 id = 5 的两条记录加了 行锁(lock_type列),并且行锁类型(lock_mode列)为:X(排它锁),还说明了不是GAP(间隙锁)。
  • 如果事务隔离级别是RR(REPEATABLE-READ,可重复读)的话,事务1执行完的锁信息如下:

RR级别下,其他事务向要往 2 ~ 7 之间插入记录就会等待获取锁。


3.2.5 插入意向锁(Insert Intention Lock)

  • 锁定对象插入操作前的间隙
  • 作用:表示事务想在某个间隙插入记录,但需要等待其他事务释放间隙锁。
  • 特点:多个插入意向锁不冲突(不同位置插入)。

4. 按加锁方式分类🎭

4.1 显式锁(Explicit Lock)

  • 定义 :通过SQL语句 显式请求 的锁。

  • 示例

    sql 复制代码
    -- 显式请求X锁
    SELECT * FROM table FOR UPDATE;
    -- 显式请求S锁
    SELECT * FROM table LOCK IN SHARE MODE;

4.2 隐式锁(Implicit Lock)

  • 定义InnoDB自动添加 的锁,无需用户请求。
  • 触发场景
    • INSERT操作 :新插入的记录会自动获得 隐式X锁
    • UPDATE/DELETE操作:被修改的记录会自动获得 隐式X锁。
    • 外键检查:检查外键约束时自动加锁。
  • 转换机制
    • 当其他事务试图访问被隐式锁定的记录时,隐式锁会 自动升级为显式锁
    • 这是一个 延迟加锁 的优化,减少不必要的锁竞争。
  • 隐式锁实现机制
    • 对于主键索引的记录来说,有一个trx_id隐藏列,该隐藏列记录着最后改动该记录的事务id。如果一个事务想对一条记录加S锁或者X锁,会先看一下该记录的 trx_id隐藏列 代表的事务是否是当前的活跃事务(即未提交事务),如果是,则该事务进入等待状态

示例

sql 复制代码
	-- 事务1
	SET transaction_isolation = 'READ-COMMITTED';
	START TRANSACTION;
	INSERT INTO cus_info (id, name) VALUES (100, '刘备'); -- 自动获得 隐式X锁
	
	-- 事务2(稍后执行)
	SET transaction_isolation = 'READ-COMMITTED';
	START TRANSACTION;
	SELECT * FROM cus_info WHERE id = 100; -- 普通查询,由于事务1没有提交,所以这里是读取不到的。
	SELECT * FROM cus_info WHERE id = 100 FOR UPDATE; -- 触发隐式锁转换,事务2等待
	UPDATE cus_info SET name = '曹操' WHERE id = 100; -- 触发隐式锁转换,事务2等待直到超时结束
	ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction -- 超时结束后输出这段提示

上面的这个示例:事务1中当插入数据时,索引记录立即创建,索引记录是物理存在的,即使事务未提交 。事务2会处于等待状态直到超时结束。事务2只是等待锁,不是读取未提交数据。事务2能看到索引记录,但看不到对应的行数据(因为未提交) 。由此也可以看出来,对于MySQL本身来说,它是能够看得到未提交事务涉及到的数据的,因为未提交事务操作的记录都在 Buffer Pool(缓冲池)中的 数据页上。


5. 锁的生命周期:获取与释放时机⏱️

++锁的 获取和释放 是针对【事务Transaction】的++。

5.1 获取锁的时机

sql 复制代码
-- 事务开始
START TRANSACTION; -- 或者 begin; 或者 set autocommit = OFF;

-- 执行SQL时获取锁(不是事务开始时)
SELECT * FROM user WHERE id = 1 FOR UPDATE; -- 此时才获取X锁

UPDATE account SET balance = balance - 100 WHERE user_id = 1; -- 此时获取X锁

5.2 释放锁的时机

  • 提交时释放COMMIT;ROLLBACK;
  • 自动释放:事务结束(连接关闭、超时等)。

重要规则

  • 锁在 事务结束后释放,不是语句执行完就释放。
  • 这就是为什么长事务会导致锁竞争严重。

"InnoDB 的锁是事务的'影子'------事务开始时获取锁,事务结束时释放锁,锁的生命周期完全绑定于事务。"


6. 锁的兼容性与冲突矩阵🔍

请求锁\现有锁 IS IX S X Gap Next-Key
IS
IX
S
X
Gap
Next-Key

7. 如何查看和诊断锁

7.1 查看当前锁信息

sql 复制代码
-- MySQL 5.7
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS;
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS;

-- MySQL 8.0+
SELECT * FROM performance_schema.data_locks;
SELECT * FROM performance_schema.data_lock_waits;

库performance_schema :表 data_locks 、表 data_lock_waits

7.2 查看事务信息

sql 复制代码
-- 查看当前运行的事务
SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;

-- 【查看锁等待关系】
SELECT 
    r.trx_id AS waiting_trx_id,
    r.trx_mysql_thread_id AS waiting_thread,
    b.trx_id AS blocking_trx_id,
    b.trx_mysql_thread_id AS blocking_thread
FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS w
JOIN INFORMATION_SCHEMA.INNODB_TRX b ON b.trx_id = w.blocking_trx_id
JOIN INFORMATION_SCHEMA.INNODB_TRX r ON r.trx_id = w.requesting_trx_id;

7.3 锁超时设置

sql 复制代码
-- 设置锁等待超时(秒)
SET innodb_lock_wait_timeout = 50;

-- 设置死锁检测
SET innodb_deadlock_detect = ON;

8. 总结💡

  1. 锁的获取和释放单位是事务

    • 锁在SQL执行时获取。
    • 锁在事务结束时(COMMIT/ROLLBACK)释放。
    • 这解释了为什么长事务是性能杀手。
  2. InnoDB锁的核心设计原则

    • 行锁为主支持高并发
    • 索引加锁:锁加在索引上,没有索引会锁表。
    • 粒度平衡:在锁开销和并发度间取得平衡。
  3. 隔离级别的影响

    • READ COMMITTED:++只有记录锁(Record Lock),无间隙锁(Gap Lock)++
    • REPEATABLE READ:++默认使用 Next-Key Lock(记录锁+间隙锁)++
    • SERIALIZABLE:所有 SELECT 都自动转为 SELECT ... FOR SHARE
  4. 最佳实践

      1. 尽量使用索引查询,避免表锁。
      1. 控制事务时长,尽快提交。
      1. 访问多个资源时,按固定顺序加锁,避免死锁。
      1. 合理设置锁等待超时时间。
相关推荐
韩立学长2 小时前
Springboot森林资源检测管理系统xowdi7nq(程序、源码、数据库、调试部署方案及开发环境)系统界面展示及获取方式置于文档末尾,可供参考。
数据库·spring boot·后端
h7997102 小时前
高效统计mysql数据踩坑笔记
数据库·mysql
爱可生开源社区2 小时前
在数据库迁移中,如何让 AI 真正“可用、可信、可落地”?
数据库·sql·llm
猿小喵2 小时前
TDSQL-MySQL相对MySQL5.7版本主从复制性能优化
数据库·mysql·性能优化
韩立学长2 小时前
【开题答辩实录分享】以《学生心理预防监控信息系统的设计与实现开题报告》为例进行选题答辩实录分享
mysql·php
姓蔡小朋友2 小时前
MySQL读写锁(元数据锁、意向锁、行锁、间隙锁、临键锁)
数据库·mysql
山峰哥2 小时前
SQL性能优化实战:从索引策略到查询优化案例全解析
大数据·数据库·sql·oracle·性能优化·架构
rannn_1112 小时前
【SQL题解】力扣高频 SQL 50题|DAY5
数据库·后端·sql·leetcode·题解
松涛和鸣2 小时前
DAY38 TCP Network Programming
linux·网络·数据库·网络协议·tcp/ip·算法