在数据库系统中,事务的隔离级别是一个非常重要的概念。它决定了事务在执行过程中如何与其他事务进行交互,以及在不同事务之间如何共享数据。事务的隔离级别越高,数据的一致性越好,但并发性能可能会降低。
脏读(Dirty Read)
脏读是指一个事务读取到另一个事务未提交的数据。如果该未提交事务最终被回滚,那么第一个事务读取的数据就是不一致的(脏的)。
示例: 假设有两个事务T1和T2,当前事务的隔离级别设置为READ UNCOMMITTED
(读未提交)。T1从表t中读取数据,表内原始有一条记录a。然后T2插入了一条新记录b,但未提交。此时,T1再次读取表t,发现有两条记录a和b。如果T2回滚,那么T1读取到的数据就是无效的。
解决方法:
-
将事务隔离级别提高到
READ COMMITTED
(读已提交)或更高。
不可重复读(Non-repeatable Read)
不可重复读是指在一个事务内,多次读取同一数据时,由于其他事务的提交,读取的结果不同。
示例: 假设有两个事务T1和T2,当前事务的隔离级别设置为READ COMMITTED
(读已提交)。T1读取表t内的数据,表内原始有一条记录a=100。然后T2修改并提交了这条记录,将a改为50。此时,T1再次读取表t,发现记录a的值变为50,与之前读取的结果不同。
解决方法:
-
将事务隔离级别提高到
REPEATABLE READ
(可重复读)或更高。
幻读(Phantom Read)
幻读是指一个事务内,针对同一查询条件的多次操作(如查询记录数),因其他事务插入或删除数据导致结果数量不一致。
示例: 假设有两个事务T1和T2,当前事务的隔离级别设置为READ COMMITTED
(读已提交)。T1查询表t内a>=100的记录数,发现有5条记录。然后T2插入了一条新记录111,并提交。此时,T1再次查询表t内a>=100的记录数,发现有6条记录。
解决方法:
-
提升隔离级别到
SERIALIZABLE
(串行化)。 -
在
REPEATABLE READ
(可重复读)下,使用Next-Key Lock
(记录锁+间隙锁)防止幻读。
MySQL中的隔离级别
MySQL提供了四种隔离级别,分别是:
-
READ UNCOMMITTED
(读未提交) -
READ COMMITTED
(读已提交) -
REPEATABLE READ
(可重复读) -
SERIALIZABLE
(串行化)
每种隔离级别解决的问题如下:
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交 | √ | √ | √ |
读已提交 | X | √ | √ |
可重复读 | X | X | √ |
串行化 | X | X | X |
解决幻读的方法
-
事务隔离级别设置为
SERIALIZABLE
。 -
在可重复读的事务级别下,给事务操作的这张表加表级锁。
-
在可重复读的事务级别下,给事务操作的这张表加
Next-Key Lock
锁(Record Lock + Gap Lock)。
InnoDB解决幻读
MySQL InnoDB引擎的默认隔离级别虽然是REPEATABLE READ
(可重复读),它很大程度上避免了幻读问题:
-
针对普通快照读(
SELECT ... FROM ...
):通过MVCC方式解决。 -
针对当前读(
SELECT ... FOR UPDATE
):通过Next-Key Lock
方式解决了幻读。
隔离级别的实现
-
读未提交:直接读取最新的数据。
-
串行化:加锁控制并行访问。
-
读已提交和读未提交:借助查询生成的
ReadView
实现。-
读已提交
是在每个语句执行之前都会重新生成一个ReadView
。 -
可重复读
隔离级别是在启动事务时生成一个ReadView
。
-