事务的隔离级别
MYSQL数据库隔离级别与并发问题分析
在数据库的并发控制中,事务隔离级别(Transaction Isolation Level)和读类型(Read Types)对解决并发问题至关重要。常见的并发问题包括脏读、不可重复读和幻读。不同的隔离级别和读类型使用不同的锁机制来解决这些问题。
隔离级别与并发问题
以下表格展示了在不同的数据库隔离级别下,快照读与当前读两种读类型对脏读、不可重复读和幻读的影响:
隔离级别 | 读类型 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|---|
读未提交 | 快照读 | 会发生 | 会发生 | 会发生 |
当前读 | 不会发生(由于读取记录时加锁,其他事务无法读取该数据,必须是两个事务都执行SELECT ... FOR UPDATE 加排它锁) | 不会发生(读取时加锁,其他事务无法修改该数据,FOR UPDATE 与LOCK IN SHARE MODE 均可) | 会发生(记录锁无法解决) | |
读已提交 | 快照读 | 不会发生(MVCC机制确保读取已提交的数据) | 会发生(每次读取会生成新的快照) | 会发生(记录锁无法解决) |
当前读 | 不会发生(通过加锁机制防止脏读) | 不会发生(排它锁确保数据不可修改) | 会发生(记录锁无法解决) | |
可重复读 | 快照读 | 不会发生(MVCC机制确保读取数据的一致性) | 不会发生(MVCC生成一次快照) | 会发生(部分情况通过MVCC可以解决) |
当前读 | 不会发生(通过加锁机制确保数据一致性) | 不会发生(通过加锁机制确保数据一致性) | 不会发生(通过间隙锁解决) | |
串行化 | 快照读 | 不会发生(自动转换为当前读) | 不会发生(自动转换为当前读) | 不会发生(自动转换为当前读) |
当前读 | 不会发生(加锁机制确保数据一致性) | 不会发生(加锁机制确保数据一致性) | 不会发生(加锁机制确保数据一致性) |
详细解释与分析
1. 读未提交(Read Uncommitted)
在该隔离级别下,事务可以读取未提交的数据,即脏读是允许发生的。因此,事务1可以读取到事务2未提交的更改,即使事务2随后回滚,事务1读取到的数据也可能不一致。除此之外,不可重复读和幻读也是允许发生的,因为没有足够的锁机制来保证数据的一致性。
- 快照读:在读取时不会加锁,其他事务可以修改数据,导致脏读、不可重复读和幻读的发生。
- 当前读:通过
SELECT ... FOR UPDATE
或LOCK IN SHARE MODE
对数据加锁,解决了脏读和不可重复读,但仍然无法解决幻读问题,因为记录锁无法防止其他事务插入数据。
2. 读已提交(Read Committed)
在该隔离级别下,事务只能读取已提交的数据,避免了脏读的发生。但是,由于每次读取会生成新的快照,不可重复读仍然可能发生。
- 快照读:每次读取会生成新的快照,导致可能出现不同的读取结果。
- 当前读:通过加锁机制(排它锁)防止数据被修改,避免了不可重复读,但依然无法解决幻读。
3. 可重复读(Repeatable Read)
该隔离级别通过MVCC机制确保在同一事务内读取到的数据始终一致,避免了脏读和不可重复读。但是,幻读依然可能发生,部分情况可以通过MVCC来部分解决。
- 快照读:MVCC机制保证了每次读取数据时返回的是事务开始时的一致性快照,避免了不可重复读和脏读,解决了大部分的幻读。如果事务1和事务2开启,事务1进行查询,事务2插入数据后提交,事务1再对事务2提交的数据进行了一个update操作,再去查询就会发生幻读
- 当前读:通过加锁机制(间隙锁)确保数据一致性,避免了不可重复读和幻读。
4. 串行化(Serializable)
该隔离级别是最严格的,事务会完全串行化执行,自动将读取操作转换为当前读,避免了所有并发问题:脏读、不可重复读和幻读。它通过强制加锁,确保事务执行时不会有其他事务插入数据或修改数据。
- 快照读:自动转换为当前读,保证了数据一致性。
- 当前读:加锁机制确保事务操作时不会受到其他事务影响,从而避免了脏读、不可重复读和幻读。
锁机制的作用
- 记录锁:在读已提交、可重复读隔离级别下,记录锁用于防止其他事务对正在读取的数据进行修改,但无法防止幻读,因为它无法阻止其他事务插入新的数据。
- 间隙锁:在可重复读和串行化隔离级别下,使用间隙锁来防止幻读。间隙锁锁定查询范围内的数据区间,从而避免其他事务在该区间插入数据。
- 排它锁(Exclusive Lock)与共享锁(Shared Lock):在当前读中,排它锁用于保证事务独占数据,而共享锁则允许其他事务进行读取,但不允许修改数据。
快照读和当前读的区别
快照读(Snapshot Read):快照读是基于MVCC(多版本并发控制)机制的一种读取方式。每个事务在启动时会创建一个自己的数据快照,这个快照是事务开始时数据库中所有数据的快照(即该事务看到的数据版本)。事务读取的数据版本就是这个快照中对应的版本,而不是当前数据库的最新版本。快照读不加锁,因此不会阻塞其他事务的操作。
当前读(Current Read):当前读是指读取数据库当前最新的数据,通常是指最新提交的数据。当前读需要加锁机制,通常使用排它锁(Exclusive Lock)或共享锁(Shared Lock)来确保数据的读取与修改的一致性。当前读会受到其他事务的影响,数据可能会被修改或者插入,因此当前读通常是一个加锁的操作
for update行锁对其他事务读操作的影响
#开启一个事务
begin;
select * from t_user_info for update;
//此时没有提交
#同时再开启一个事务
begin;
select * from t_user_info for update;
commit;
这种情况下,第二个事务是会被阻塞的
#开启一个事务
begin;
select * from t_user_info for update;
//此时没有提交
#同时再开启一个事务
begin;
select * from t_user_info;
commit;
这种情况下,第二个事务不会被阻塞的
#开启一个事务
begin;
select * from t_user_info for update;
//此时没有提交
#同时再开启一个事务
begin;
select * from t_user_info lock in share mode;
commit;
这种情况下,第二个事务是会被阻塞的
排它锁会阻止其他事务对数据加共享锁或排它锁。
共享锁允许其他事务也加共享锁,但会阻止其他事务对数据加排它锁。