漫谈: 数据库脏读, 不可重复读, 幻读

前提: 阅读本文, 需要有一定的并发多线程的"脑图"概念, 提升事务隔离等级, 可以解决以上问题, 但是目前我还没搞懂事务隔离等级的原理, 所以这篇文章只能帮助你如何避免数据库并发安全问题. 
为何要深度理解?

1. 如果不明白这三个理论的原理, 可能在进行数据库sql业务代码上存在并发安全问题;

2. 基本上是面试必问的问题;

3. 程序员就应该学的东西;

何为脏读(Dirty read)?

官方定义: 事务A读到了事务B中修改但是没有提交的数据, 事务B最后ROLLBACK了. 如果事务A没有提交的情况下, 那只违反了事务的隔离性(ISOLATION)特性, 如果提交了, 不仅仅会违反隔离性,还会违反一致性(Consisdency); 

图解:

代码实现:

我们使用MySQL来实现代码, 因为MySQL的默认事务隔离级别是READ COMMITTED, 所以必须手动将事务A&事务B的事务隔离级别设置为READ UNCOMMITTED 意为可以"事务可以读取未提交的数据";

上图为测试使用的表:

事务A:

-- 设置隔离级别为 READ UNCOMMITTED
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

-- 开始事务 A
START TRANSACTION;

-- 读取数据(发生脏读)
SELECT balance FROM accounts WHERE id = 1;

事务B:

-- 开始事务 B
START TRANSACTION;

-- 修改数据,但未提交
UPDATE accounts SET balance = balance - 100.00 WHERE id = 1;

-- 事务 B 尚未提交
-- ROLLBACK;

模拟: 可以让事务B先运行, 然后事务A后运行来模拟并发效果;

运行事务B, 查看表accounts(此处是逻辑表数据, 并没有持久化):

发现余额变成了1900, 但实际表中还是2000因为没做提交(commit);

运行事务A, 查询结果如下:

事务A读取了事务B未提交的数据. 

事务B执行回滚后的表数据:

以上呢, 就是演示了脏读出现过程, 如果事务B成功执行了Commit, 并不会产生脏读! 所以脏读是一个事务读取了另一个事务的未提交数据, 而发生的并发安全现象;

如何避免脏读?

将事务的隔离级别设置为: READ COMMITTED/ REPEATABLE READ/ SERIALIZABLE 即可;

mysql默认的事务隔离级别是REPEATABLE READ, 所以不需要设置;

READ COMMITTED/ REPEATABLE READ/ SERIALIZABLE 解决脏读的原理, 设置为以上隔离等级的事务只能读取已提交的数据, 当其他事务正在更新数据且还未提交, 此事务会阻塞, 直到等到其他事务回滚或者提交. (这只是个大概理解)

何为不可重复读(Non-Repeatable Read)?

官方定义: 对于两个事务A, B, A 读取了一个字段, 然后 B 更新了该字段。之后, A再次读取同一个字段, 值就不同了。

图解:

不可重复读是怎样的并发安全问题呢? 它会引发事务A的业务混乱, 如果tom的余额在事务B提交的前后都参与了其他字段的运算, 并做了Commit, 那毫无疑问, 数据表数据将不一致;

代码实现:

事务A:

-- 设置隔离级别为 READ UNCOMMITTED
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

-- 开始事务 A
START TRANSACTION;

-- 第一次 读取数据
SELECT balance FROM accounts WHERE id = 1;

-- 第二次 读取数据, 在执行事务B之后执行, 以模拟并发;
SELECT balance FROM accounts WHERE id = 1;

这里的隔离界别仍然设置为 READ UNCOMMITTED: 因为此隔离级别无法防止事务的脏读/幻读/不可重复读;

事务B:

-- 开始事务 B
START TRANSACTION;

-- 修改数据,但未提交
UPDATE accounts SET balance = balance - 100.00 WHERE id = 1;

-- 事务 B 提交
COMMIT;

首先执行事务A 的第一个查询语句, 查询结果如下:

然后执行事务B, 更新数据, 并持久化;

最后执行事务A最后一条查询语句:

这就造成了, 在事务A中两次查询拿到的数值不一样!!!

解决方法:

将事务A的事务隔离等级设置为REPEATABLE READ/ SERIALIZABLE 即可:

恢复初始数据,重新测试:

首先执行事务A 的第一个查询语句, 查询结果如下:

然后执行事务B, 更新数据, 并持久化;

最后执行事务A最后一条查询语句:

可见, 事务A第二条查询拿到的数据依然是和第一条查询拿到的是一模一样的;

那么有个问题, 为什么设置隔离等级为REPEATABLE READ/ SERIALIZABLE 就可以规避不可重复读呢?

这个问题答案以后会补充:

何为幻读(Phantom Read)?

官方定义: 对于个事务A,B, A 从一个表中读取了一个字段,然后 B 在该表中插入了一些新的行。之后,  果 A 再次读取同一个表, 就会多出几行。

幻读, 我暂时就不介绍了, 因为我设置事务A的隔离等级为 REPEATABLE READ 同样能避免幻读, 查询了相关资料后, 发现REPEATABLE READ确实能避免一些幻读, 但不能完全避免, 这里涉及到了锁的概念. 待到以后自己明白之后, 再做进一步分析, 现在直到如何避免幻读即可(设置隔离等级为SERIALIZABLE);

  • 22
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值