在日常开发过程中,事务是经常被使用的,然而大多数开发者并不了解事务隔离级别是什么,也不知道不同的隔离级别下使用事务时可能会发生的一些问题。
SQL标准定义的四个隔离级别为:
- READ UNCOMMITTED (读未提交)
- READ COMMITTED (读已提交)
- REPEATABLE READ (可重复读)
- SERIALIZABLE (串行)
user表
id | name | money |
---|---|---|
1 | 小李 | 1000 |
READ UNCOMMITTED
事务A修改了一条数据,但未提交,事务B可以读到被事务A修改的数据,存在脏读问题,同时也存在不可重复读、幻读等问题(不一一写实际例子)。
事务A | 事务B |
---|---|
start TRANSACTION | start TRANSACTION |
update user set name = '小明' where id = 1 | - |
- | select name from user where id = 1 |
READ COMMITTED
解决了脏读的情况,但是当事务A提交之后,事务B在一次事务内读取同一条数据会有不同的结果,存在不可重复读的问题。
事务A | 事务B |
---|---|
start TRANSACTION | start TRANSACTION |
update user set name = '小明' where id = 1 | - |
- | select name from user where id = 1 |
COMMIT | - |
- | select name from user where id = 1 |
REPEATABLE READ
Mysql默认的事务隔离级别,解决了脏读、不可重复读问题,同时用
next_key_lock 解决了幻读的问题,但是有出现更新覆盖的可能。
事务A | 事务B |
---|---|
start TRANSACTION | start TRANSACTION |
select money into @money from user where id = 1 | select money into @money from user where id = 1 |
update user set money = @money-100 where id = 1 | - |
COMMIT | - |
- | update user set money = @money-50 where id = 1 |
- | COMMIT |
对应实际的业务场景,就是用户分别消费了100元和50元,2个事务都成功了,但是实际上最终账户只减少了50元。
解决这种情况,需要在读取数据的时候加上排他锁(X锁),使2个事务变成串行操作。如下:
事务A | 事务B |
---|---|
start TRANSACTION | start TRANSACTION |
select money into @money from user where id = 1 for update | select money into @money from user where id = 1 for update |
update user set money = @money-100 where id = 1 | - |
COMMIT | - |
- | update user set money = @money-50 where id = 1 |
- | COMMIT |
SERIALIZABLE
串行模式下,可以解决事务相互依赖导致的死锁问题,以及REPEATABLE READ下可能出现的更新被覆盖问题,但是由于不能同时执行2个或以上的事务,会使性能下降,所以大部分情况下不会选择此模式。
网友评论