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. 合理设置锁等待超时时间。
相关推荐
jiunian_cn1 小时前
【Redis】hash数据类型相关指令
数据库·redis·哈希算法
冉冰学姐1 小时前
SSM在线影评网站平台82ap4(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面
数据库·ssm框架·在线影评平台·影片分类
Exquisite.2 小时前
企业高性能web服务器(4)
运维·服务器·前端·网络·mysql
知识分享小能手2 小时前
SQL Server 2019入门学习教程,从入门到精通,SQL Server 2019数据库的操作(2)
数据库·学习·sqlserver
踩坑小念3 小时前
秒杀场景下如何处理redis扣除状态不一致问题
数据库·redis·分布式·缓存·秒杀
萧曵 丶4 小时前
MySQL 语句书写顺序与执行顺序对比速记表
数据库·mysql
Wiktok5 小时前
MySQL的常用数据类型
数据库·mysql
曹牧5 小时前
Oracle 表闪回(Flashback Table)
数据库·oracle
J_liaty5 小时前
Redis 超详细入门教程:从零基础到实战精通
数据库·redis·缓存
m0_706653235 小时前
用Python批量处理Excel和CSV文件
jvm·数据库·python