一、事务的ACID特性
在深入了解事务隔离级别之前,先回顾一下数据库事务的四大核心特性——ACID:
| 特性 | 英文 | 说明 |
|---|---|---|
| 原子性(Atomicity) | Atomicity | 事务中的操作要么全部成功,要么全部失败回滚,不存在部分执行的中间状态 |
| 一致性(Consistency) | Consistency | 事务执行前后,数据库的完整性约束(如主键、外键、业务规则)保持有效 |
| 隔离性(Isolation) | Isolation | 并发事务之间相互隔离,一个事务的执行不能被其他事务干扰 |
| 持久性(Durability) | Durability | 事务提交后,对数据的修改永久保存,即使系统崩溃也不会丢失 |
关键点:一致性(C)是业务层面的目标,需要原子性(A)、隔离性(I)、持久性(D)这三个机制共同保障。
DDL 本身不支持事务、不能回滚
MySQL 事务里插 DDL = 截断旧事务、提交旧数据、新开新事务
二、什么是事务隔离
事务隔离是数据库事务ACID特性中"I"(Isolation,隔离性)的体现。它定义了并发事务之间相互影响的程度。隔离级别越高,数据一致性越好,但并发性能越差。
三、事务并发带来的问题
在了解隔离级别之前,先看看并发事务可能引发的问题:
| 问题 | 说明 |
|---|---|
| 脏读(Dirty Read) | 读取到其他事务未提交的数据 |
| 不可重复读(Non-Repeatable Read) | 同一事务中,两次读取同一行数据结果不同(针对 UPDATE) |
| 幻读(Phantom Read) | 同一事务中,两次查询结果集行数不同(针对 INSERT/DELETE) |
四、四种隔离级别(SQL标准)
SQL标准定义了四种隔离级别,从低到高依次为:
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| READ UNCOMMITTED | 可能 | 可能 | 可能 |
| READ COMMITTED | 不可能 | 可能 | 可能 |
| REPEATABLE READ | 不可能 | 不可能 | 可能(SQL标准) |
| SERIALIZABLE | 不可能 | 不可能 | 不可能 |
说明:SQL标准的REPEATABLE READ级别允许幻读,但MySQL InnoDB的RR级别通过MVCC+Next-Key Lock机制实现了对幻读的防护,是对SQL标准的增强。
1. READ UNCOMMITTED(读未提交)
核心规则:允许读取其他事务未提交的数据。
这是最低的隔离级别,几乎不提供任何并发保护。
示例:
-- 事务A:更新数据但未提交
BEGIN;
UPDATE account SET balance = balance - 100 WHERE id = 1;
-- 事务B:读取到了事务A未提交的数据
SELECT balance FROM account WHERE id = 1; -- 读到的是修改后的值
-- 事务A:回滚
ROLLBACK;
-- 事务B:读到的数据是脏数据(事务A已回滚)
SELECT balance FROM account WHERE id = 1; -- 数据不一致
适用场景:几乎不使用,仅在需要极高并发且能容忍脏读的场景下考虑。
2. READ COMMITTED(读已提交)
核心规则:只能读取已提交事务的数据,防止脏读。
大多数数据库(如Oracle、SQL Server)的默认隔离级别。
示例:
-- 事务A:更新数据但未提交
BEGIN;
UPDATE account SET balance = balance - 100 WHERE id = 1;
-- 事务B:无法读取到未提交的数据
SELECT balance FROM account WHERE id = 1; -- 读到的是修改前的值
-- 事务A:提交
COMMIT;
-- 事务B:现在可以读到新值
SELECT balance FROM account WHERE id = 1; -- 读到新值
存在的问题:不可重复读。同一个事务中,两次读取同一行数据可能得到不同结果。
3. REPEATABLE READ(可重复读)
核心规则:同一事务中,多次读取同一数据结果相同,即使其他事务修改并提交了该数据。
重要说明:此规则仅适用于快照读(普通SELECT)。如果是当前读,会读取最新的已提交数据并加锁。
MySQL(InnoDB)的默认隔离级别。SQL标准的RR级别允许幻读,但InnoDB的RR级别是对SQL标准的增强,通过MVCC+Next-Key Lock共同解决了幻读问题。
核心机制:
InnoDB在RR级别下,通过两种完全不同的机制分别解决两种读操作的幻读问题:
| 读操作类型 | 机制 | 说明 |
|---|---|---|
| 快照读(普通SELECT) | MVCC(多版本并发控制) | 事务开启时生成一致性视图(Read View),整个事务期间所有快照读都基于此视图 |
| 当前读(SELECT ... FOR UPDATE/LOCK IN SHARE MODE、INSERT、UPDATE、DELETE) | Next-Key Lock(临键锁=记录锁+间隙锁) | 锁定查询条件对应的索引范围(包括间隙),阻止其他事务插入新行 |
示例1:快照读的可重复读特性
-- 事务A:开启事务,快照读
BEGIN;
SELECT balance FROM account WHERE id = 1; -- 快照读,返回1000
-- 事务B:更新数据并提交
BEGIN;
UPDATE account SET balance = 500 WHERE id = 1;
COMMIT;
-- 事务A:再次快照读,结果与第一次相同
SELECT balance FROM account WHERE id = 1; -- 快照读,仍然返回1000(符合可重复读)
-- 事务A:当前读,读取最新值并加锁
SELECT balance FROM account WHERE id = 1 FOR UPDATE; -- 当前读,返回500
-- 事务A:提交
COMMIT;
示例2:快照读通过MVCC避免幻读
-- 事务A:开启事务,快照读
BEGIN;
SELECT * FROM user WHERE age > 20; -- 返回5行(快照读,基于Read View)
-- 事务B:插入新用户并提交
INSERT INTO user (name, age) VALUES ('新用户', 25);
COMMIT;
-- 事务A:再次快照读,不会看到新插入的行(MVCC一致性视图)
SELECT * FROM user WHERE age > 20; -- 仍然返回5行
COMMIT;
示例3:当前读通过Next-Key Lock避免幻读
-- 事务A:开启事务,当前读(加锁)
BEGIN;
SELECT * FROM user WHERE age > 20 FOR UPDATE; -- 返回5行,锁定索引范围(20, +∞)
-- 事务B:尝试插入,被阻塞(等待事务A释放锁)
INSERT INTO user (name, age) VALUES ('新用户', 25); -- 阻塞!
-- 事务A:提交后,事务B才能继续
COMMIT;
4. SERIALIZABLE(串行化)
核心规则:最高隔离级别,事务以串行方式执行,完全避免并发问题。
核心机制:InnoDB的SERIALIZABLE级别会自动将所有普通SELECT转换为SELECT ... FOR SHARE(共享锁)。这意味着:
- 多个事务可以同时读取同一行(共享锁兼容)
- 任何事务都不能修改被其他事务读取的行(共享锁与排他锁互斥)
- 从而实现了事务的完全串行化执行
示例:
-- 事务A:开启事务,自动加共享锁
BEGIN;
SELECT * FROM account WHERE id = 1; -- 自动转换为 SELECT ... FOR SHARE
-- 事务B:尝试更新同一行,被阻塞(共享锁与排他锁互斥)
UPDATE account SET balance = 500 WHERE id = 1; -- 阻塞,等待事务A释放锁
-- 事务A:提交后,事务B才能继续
COMMIT; -- 事务B此时才能执行
缺点:并发性能极差,容易出现大量锁等待和死锁。
五、MySQL中的设置
查看当前隔离级别
-- MySQL 8.0+
SELECT @@global.transaction_isolation;
SELECT @@session.transaction_isolation;
SELECT @@transaction_isolation;
-- MySQL 5.7及更早版本(兼容写法)
SELECT @@global.tx_isolation;
SELECT @@session.tx_isolation;
SELECT @@tx_isolation;
设置隔离级别
-- MySQL 8.0+
SET GLOBAL transaction_isolation = 'REPEATABLE-READ';
SET SESSION transaction_isolation = 'READ-COMMITTED';
SET transaction_isolation = 'SERIALIZABLE';
-- MySQL 5.7及更早版本(兼容写法)
SET GLOBAL tx_isolation = 'REPEATABLE-READ';
SET SESSION tx_isolation = 'READ-COMMITTED';
SET tx_isolation = 'SERIALIZABLE';
启动参数设置
# my.cnf 或 my.ini
[mysqld]
transaction-isolation = REPEATABLE-READ
六、隔离级别对比总结
| 隔离级别 | 性能 | 一致性 | 并发问题 | 核心实现机制(InnoDB) |
|---|---|---|---|---|
| READ UNCOMMITTED | 最高 | 最差 | 脏读、不可重复读、幻读 | 无 |
| READ COMMITTED | 较高 | 较差 | 不可重复读、幻读 | 语句级MVCC |
| REPEATABLE READ | 中等 | 较好 | 幻读(InnoDB通过MVCC+Next-Key Lock共同避免) | 事务级MVCC(快照读)+ Next-Key Lock(当前读) |
| SERIALIZABLE | 最低 | 最好 | 无(完全串行化) | 所有读加共享锁,完全串行化 |
七、实际选择建议
MySQL默认REPEATABLE-READ:适合大多数业务场景,InnoDB通过MVCC和锁机制在保证一定并发性的同时防止脏读、不可重复读和幻读。大多数互联网业务使用MySQL默认的RR级别即可。
金融、账务类系统:可考虑SERIALIZABLE或使用分布式锁确保强一致性。只有对一致性要求极高的场景(如银行核心账务)才考虑SERIALIZABLE。
高并发查询类系统:可考虑READ-COMMITTED,减少锁竞争提升性能。
测试环境想重现并发问题:使用READ-UNCOMMITTED。
八、注意事项
隔离级别不是越高越好:应根据业务场景选择合适的隔离级别,避免过度加锁导致性能问题。
MySQL RR级别防止幻读:通过MVCC(快照读)+ Next-Key Lock(当前读)共同实现,比标准RR更严格。
长事务风险:高隔离级别下,长事务会持有大量锁,影响并发性能,应尽量避免。在InnoDB RR级别下,长事务还会导致undo log无法清理,因为MVCC需要保留旧版本数据供长事务的快照读使用,长事务运行时间越长,积累的undo log就越多,可能导致磁盘空间暴涨,甚至影响数据库性能。
分布式事务:单库隔离级别不适用于跨库场景,需使用分布式事务协议(如XA)或应用层协调。