MySQL 事务隔离级别与加锁机制详解
在并发环境下,MySQL 通过不同的锁类型来保障数据一致性。本文详细解析读未提交、读已提交和可重复读三种隔离级别下的加锁行为,说明记录锁、间隙锁及 Next-Key 锁的应用差异,并解释为何 InnoDB 采用可重复读作为默认隔离级别以解决幻读问题。
MySQL 锁机制基础
数据库引入事务隔离级别的核心目的是解决并发访问导致的数据一致性问题(如脏读、不可重复读、幻读)。全局锁、表锁、行锁等是隔离级别的具体实现手段,而 MVCC 和意向锁则是性能优化的辅助机制。
InnoDB 存储引擎的行级锁主要分为以下三类:
- 记录锁(Record Lock):锁定索引记录本身。
- 间隙锁(Gap Lock):锁定两个索引记录之间的间隙,防止其他事务在间隙中插入数据。
- Next-Key Lock:记录锁与间隙锁的组合,锁定索引记录及其之前的间隙。
我们可以通过查看 InnoDB 状态来观察锁的详细信息。首先开启锁日志输出,然后查询引擎状态:
set global innodb_status_output_locks=on; show engine innodb status\G;
在返回结果中,关键关键字如下:
- 表级意向排它锁:`lock mode IX`
- 行级记录锁:`lock_mode X locks rec but not gap`
- 行级间隙锁:`lock_mode X locks gap before rec`
- Next-Key 锁:`lock_mode X`(无额外标识时通常指 Next-Key 锁或记录锁,需结合上下文判断)
读未提交(READ UNCOMMITTED)
该隔离级别允许读取未提交的数据,会产生“脏读”现象,且不加任何特殊的行锁来阻止读取。
第 1 步:创建测试环境与数据准备
创建一个包含主键和价格的测试表,并插入初始数据:
CREATE TABLE price_test (
id BIGINT(64) NOT NULL AUTO_INCREMENT,
name varchar(32) not null,
price INTEGER(4) NULL,
PRIMARY KEY (id)
);
INSERT INTO price_test(name,price) values('apple', 10);
INSERT INTO price_test(name,price) values('orange', 30);
第 2 步:配置事务隔离级别
在会话中将隔离级别设置为“读未提交”:
SET session TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SELECT @@transaction_isolation;
第 3 步:验证读写并发行为
执行以下操作:
- 事务 A 开启事务并查询
id=1的记录。 - 事务 B 更新
id=1的价格为20,但不提交。 - 事务 A 再次查询
id=1。
可以看到,事务 A 读取到了事务 B 尚未提交的修改数据,证实了脏读的存在。
第 4 步:验证写写阻塞行为
同时更新同一条记录:事务 A 将价格改为 15,此时未提交;事务 B 尝试将价格改为 20。
事务 B 会被阻塞,因为它试图获取该行锁,但行锁已被事务 A 持有。当事务 A 回滚后,事务 B 获得锁并执行成功。
查看锁信息可见,此时使用的是行级记录锁 lock_mode X locks rec but not gap。
第 5 步:非索引条件查询测试
假设按 price > 15 进行 for update 查询:
BEGIN; SELECT * FROM price_test WHERE price > 15 FOR UPDATE;
另一个事务尝试查询 price > 5 并阻塞。
查看锁信息显示仅加了记录锁,未加间隙锁:
结论:读未提交级别下,读写不阻塞,写写会阻塞(依赖记录锁),不涉及间隙锁。
读已提交(READ COMMITTED)
该隔离级别解决了脏读问题,但仍可能存在不可重复读。
第 1 步:设置隔离级别
SET session TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT @@transaction_isolation;
第 2 步:更新同一记录
事务 A 更新 id=1 为 15,事务 B 尝试更新为 20。
事务 B 依然会被阻塞,因为行记录锁的行为未变。
锁类型为:`lock_mode X locks rec but not gap`。
第 3 步:范围查询测试
事务 A:SELECT ... WHERE price > 5 FOR UPDATE
事务 B:SELECT ... WHERE price > 15 FOR UPDATE
事务 B 阻塞,查看锁信息:
结果显示仍仅为记录锁,没有间隙锁。与读未提交相比,最大的区别在于读取数据时会忽略未提交的版本(MVCC 快照),从而避免了脏读,但在并发更新时的锁机制相似。
可重复读(REPEATABLE READ)
这是 MySQL InnoDB 的默认隔离级别。它不仅解决脏读和不可重复读,还通过 Next-Key 锁基本解决了幻读问题。
第 1 步:设置隔离级别
SET session TRANSACTION ISOLATION LEVEL REPEATABLE READ; SELECT @@transaction_isolation;
第 2 步:更新同一记录
同前,事务 A 和 B 更新 id=1 时,B 依然被阻塞,使用记录锁。
第 3 步:范围查询触发 Next-Key 锁
事务 A:SELECT ... WHERE price > 5 FOR UPDATE
事务 B:SELECT ... WHERE price > 15 FOR UPDATE
由于使用了 FOR UPDATE 即加写锁,且在该隔离级别下需要防止幻读,InnoDB 会使用 Next-Key Lock。
查看锁信息显示为 `lock_mode X`(此处对应 Next-Key Lock 逻辑),这意味着不仅锁住了符合查询条件的记录,还锁住了相关间隙,阻止其他事务插入满足条件的数据,从而消除幻读。
总结
不同隔离级别的加锁策略可以归纳为:
- 读未提交、读已提交:主要使用记录锁,不使用间隙锁。读未提交允许脏读;读已提交通过 MVCC 版本控制解决脏读。
- 可重复读:使用记录锁、间隙锁以及它们的组合——Next-Key 锁。通过加间隙锁封锁潜在的新增数据区域,有效抑制了幻读的发生,这也是其作为 MySQL 默认隔离级别的重要原因。
以上就是 MySQL 不同隔离级别加锁机制的详细内容,更多关于 MySQL 事务隔离的内容请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













