MySQL事务四大隔离级别
MySQL事务四大隔离级别
- 1. 并发事务问题
- 1.1 脏读(Dirty Read)
- 1.2 不可重复读(Non-repeatable Read)
- 1.3 幻读(Phantom Read)
- 2. 事务隔离级别
- 3. 隔离级别的底层实现原理
- READ UNCOMMITTED(读未提交)
- READ COMMITTED(读已提交)
- REPEATABLE READ(可重复读)
- SERIALIZABLE(可串行化)
- 4. 四大隔离级别的比较
- 5. 多版本并发控制(MVCC)
- 5.1 MVCC 实现原理
- 5.2 幻读的解决方法
- 6. 选择合适的隔离级别
1. 并发事务问题
1.1 脏读(Dirty Read)
脏读
指的是一个事务读取了另一个事务尚未提交的数据。当第二个事务对数据进行了修改并提交时,第一个事务读取的数据是 “脏的” —— 即它读取到的是一个不稳定、未完成的状态。
发生场景:
事务 A 更新了某一数据,但尚未提交(即数据处于未提交状态)
事务 B 读取了事务 A 更新的数据
如果事务 A 最后回滚(未提交),那么事务 B 读取到的数据实际上是无效的
问题:
脏读会导致事务读取到不一致的、不可靠的数据
解决方法:
通过 READ COMMITTED 或更高级的隔离级别来避免脏读。READ UNCOMMITTED 是允许脏读的最低隔离级别
示例:
假设有两个事务,事务 A 和事务 B:
-- 事务 A
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE user_id = 1; -- 修改数据但未提交-- 事务 B
BEGIN;
SELECT balance FROM accounts WHERE user_id = 1; -- 事务 B 读取到了事务 A 修改但未提交的数据-- 事务 A
ROLLBACK; -- 事务 A 回滚,余额恢复
此时,事务 B 读取到了事务 A 还没有提交的、更改过的余额数据。这个数据称为 “脏数据”,因为它可能会被撤销。
1.2 不可重复读(Non-repeatable Read)
不可重复读
指的是在一个事务中,读取同一数据两次(或多次),但是每次读取的结果不同。通常是因为在两次读取之间,其他事务修改了该数据
发生场景:
事务 A 在第一次读取数据时获取了某个数据的值。
事务 B 在事务 A 的读取之后,更新了该数据并提交。
当事务 A 再次读取相同数据时,得到了不同的值。
问题:
不可重复读会导致事务之间的数据不一致,影响了数据的稳定性和可靠性。事务 A 在同一事务内获取的数据应该保持一致,而不可重复读会破坏这一点
解决方法:
通过 REPEATABLE READ 或更高级的隔离级别来避免不可重复读。READ COMMITTED 是允许不可重复读的隔离级别。
示例:
假设有两个事务,事务 A 和事务 B:
-- 事务 A
BEGIN;
SELECT balance FROM accounts WHERE user_id = 1; -- 初次读取余额-- 事务 B
BEGIN;
UPDATE accounts SET balance = balance + 50 WHERE user_id = 1; -- 事务 B 修改了余额并提交
COMMIT;-- 事务 A
SELECT balance FROM accounts WHERE user_id = 1; -- 事务 A 再次读取余额,得到了不同的值
在上面的例子中,事务 A 在两次读取之间,事务 B 对数据进行了修改并提交,这导致事务 A 的两次读取结果不同。
1.3 幻读(Phantom Read)
幻读
指的是事务 A 在执行某个查询时,看到的数据集与后续执行同一查询时的结果不同。通常是因为其他事务插入、删除或更新了数据,使得查询结果发生变化
发生场景:
事务 A 执行了一个范围查询(如 SELECT * FROM accounts WHERE balance > 100)。
事务 B 在事务 A 执行查询后,插入或删除了满足该查询条件的记录。
当事务 A 再次执行相同的查询时,查询结果发生了变化,出现了新的行或者删除了原先的行
问题:
幻读会导致查询结果的不一致,特别是在事务处理中涉及到范围查询或聚合操作时,可能会出现不可预期的结果
解决方法:
通过 SERIALIZABLE 隔离级别来解决幻读
示例:
假设有两个事务,事务 A 和事务 B:
-- 事务 A
BEGIN;
SELECT COUNT(*) FROM accounts WHERE balance > 100; -- 查询符合条件的记录数-- 事务 B
BEGIN;
INSERT INTO accounts (user_id, balance) VALUES (2, 150); -- 事务 B 插入新记录
COMMIT;-- 事务 A
SELECT COUNT(*) FROM accounts WHERE balance > 100; -- 事务 A 再次查询,结果发生变化,出现幻读
在上面的例子中,事务 A 在第一次查询时,看到符合条件的记录数。事务 B 插入了一个新的记录,事务 A 再次查询时,发现符合条件的记录数发生了变化。
2. 事务隔离级别
MySQL 的事务隔离级别是为了在并发环境中保持数据的一致性和完整性。为了确保事务的 ACID(原子性、一致性、隔离性、持久性)特性,MySQL 提供了四种隔离级别,它们定义了事务间如何彼此隔离以及如何处理不同事务对同一数据的并发操作。每种隔离级别在处理 脏读、不可重复读、幻读 这三个问题上有不同的策略
MySQL 支持四种隔离级别:
-
READ UNCOMMITTED(读未提交)
-
READ COMMITTED(读已提交)
-
REPEATABLE READ(可重复读)
-
SERIALIZABLE(可串行化)
它们的隔离性从低到高依次是:
READ UNCOMMITTED < READ COMMITTED < REPEATABLE READ < SERIALIZABLE
3. 隔离级别的底层实现原理
MySQL 默认使用 REPEATABLE READ 隔离级别,其实现与 InnoDB 存储引擎密切相关。不同的隔离级别会通过不同的锁机制和版本控制策略来保证并发操作时的数据一致性
READ UNCOMMITTED(读未提交)
底层实现:
-
锁机制:没有加锁。事务可以读到未提交的事务所做的修改。
-
并发控制:允许脏读(dirty read)。也就是说,事务 A 更新了数据,但事务 A 还没有提交,事务 B 可以读取到事务 A 尚未提交的数据
问题:
-
脏读:事务可以读取到其他事务尚未提交的脏数据
-
不可重复读:读取数据的值在同一事务内可能发生变化
使用场景:
适用于对数据一致性要求不高的场景,通常用于性能要求高的临时数据查询
READ COMMITTED(读已提交)
底层实现:
-
锁机制:每次读取数据时,都会加 共享锁,保证读取的数据是已提交的
-
并发控制:避免了脏读,但允许不可重复读。事务 A 已经提交的数据可以被其他事务读取,但如果在同一事务中读取相同数据时,可能会得到不同的值
问题:
-
脏读:不会发生,因为只有已提交的数据才可被读取
-
不可重复读:事务中多次读取同一数据时,可能得到不同的结果
使用场景:
适用于数据一致性要求较高的场景,如需要避免脏读的查询,但容忍不可重复读。
REPEATABLE READ(可重复读)
底层实现:
-
锁机制:对于读取的数据,会加 共享锁,保证后续读取到的数据不被其他事务修改
-
并发控制:避免了脏读和不可重复读,但存在 幻读 的问题。也就是说,事务读取了一定范围的数据,但是其他事务可能在事务期间向表中插入或删除记录,导致同一查询再次执行时返回的数据集不同
解决幻读:
多版本并发控制(MVCC):InnoDB 使用了多版本并发控制来避免不可重复读和幻读。在此隔离级别下,InnoDB 通过 Undo Log 和 快照读 实现,保持数据的稳定性
问题:
- 幻读:事务 A 在读取某个范围的记录后,事务 B 在其未提交之前修改了数据(例如插入或删除数据),事务 A 再次查询时返回不同的结果
使用场景:
默认的隔离级别,适用于大多数需要避免脏读和不可重复读的业务场景,但如果要防止幻读,可能需要额外的锁定策略
SERIALIZABLE(可串行化)
底层实现:
-
锁机制:每次读取的数据都会加 排他锁(X-lock),保证没有其他事务可以修改或读取数据,直到当前事务完成
-
并发控制:完全避免了脏读、不可重复读和幻读。事务完全串行化执行
问题:
- 性能影响:由于每次读取数据都会加排他锁,导致并发性能非常低。尤其是在高并发的情况下,可能导致大量的阻塞,影响性能
使用场景:
适用于对数据一致性要求极高的场景,比如金融系统的关键数据操作
4. 四大隔离级别的比较
四种隔离级别可以解决的问题
隔离级别 | 不可重复读 | 幻读 | 实现方式 |
---|---|---|---|
READ UNCOMMITTED | × | × | × |
READ COMMITTED | √ | × | × |
REPEATABLE READ | √ | √ | × |
SERIALIZABLE | √ | √ | √ |
5. 多版本并发控制(MVCC)
Multi Version Concurrency Control
在 REPEATABLE READ 隔离级别中,MySQL 的 InnoDB 引擎使用了 多版本并发控制(MVCC) 来避免不可重复读和幻读的问题。
让读写不再冲突
5.1 MVCC 实现原理
-
快照读:通过 MVCC,在 REPEATABLE READ 隔离级别下,即使有事务修改了数据,查询时也会读取历史版本的数据,保证同一事务中的数据一致性
当前读:读取当前最新数据 的操作,它会考虑到其他事务的修改结果,并且会阻塞等待,直到获取到最新的数据
-
Undo Log:每当数据被修改时,InnoDB 会将修改前的值保存在 Undo Log 中。查询操作会读取与当前事务快照一致的数据版本,而不是当前最新的数据版本,从而避免不可重复读的问题
-
读取未提交的数据版本:在执行 SELECT 查询时,InnoDB 会基于事务的 start timestamp 查找合适的数据版本。通过这个机制,InnoDB 可以实现高效的事务隔离
5.2 幻读的解决方法
在 REPEATABLE READ 隔离级别下,InnoDB 会将一部分 间隙锁(gap locks) 应用于范围查询中,以阻止其他事务插入新的记录,从而防止幻读现象
间隙锁(Gap Lock) 是 InnoDB 存储引擎中的一种锁类型,是一种 行锁 ,确保其他事务不能在该范围内插入新的数据
6. 选择合适的隔离级别
低并发、数据一致性要求高:使用 SERIALIZABLE 隔离级别,保证绝对的数据一致性
需要平衡性能和一致性:使用 REPEATABLE READ 隔离级别,保证较高的一致性,并避免脏读和不可重复读
高并发、对一致性要求不高:可以考虑 READ COMMITTED 或 READ UNCOMMITTED,但需要特别注意脏读和不可重复读的问题
总结
MySQL 事务的四大隔离级别提供了不同的并发控制和一致性保障,理解它们的底层实现机制对于优化数据库性能和确保数据的完整性至关重要。InnoDB 通过 MVCC 和 间隙锁 等技术,提供了对数据一致性的保障,并在不同的隔离级别下通过锁机制和版本控制来平衡数据一致性和并发性能。