为了更好理解下文,这里先给出个业务场景:
老板:原来陈哈哈是我失散多年的大侄子!财务,给 “陈哈哈” 的工资涨 “10000” 大洋。
陈哈哈:谢谢老叔!MUA~
并发场景下事务存在的数据问题
==============
下面我们介绍一下脏读(dirty read)、不可重复读(non-repeatable read)、幻读(phantom read)这三类并发问题,以及每种问题出现的原理及场景。
- 1. 脏读(针对的是未提交读数据)
事务A修改了数据,但未提交,而事务B查询了事务A修改过却没有提交的数据,这就是脏读,因为事务A可能会回滚。
**场景:**老板(老叔)大喊了一嗓子,但没有指定哪个财务改。财务A大姐和财务B大哥都听到了,但他俩不知道由谁来改,就分别进行了下方流程操作:
时间点 | 事务A(财务大姐) | 事务B(财务大哥) | 陈哈哈 |
T1 | (财务大姐要给我改,查到工资:3000) Begin; SELECT PAY from department where `NAME` = '陈哈哈'; |
| |
T2 | UPDATE department SET ` PAY` = ` PAY` + 10000 where `NAME` = '陈哈哈'; | (财务大哥看看财务大姐改没改) Begin; | |
T3 | (财务大姐突然发现财务大哥也在给我改) | (一查发现。哦!已经改了)(工资:13000) SELECT PAY from department where `NAME` = '陈哈哈'; | |
T4 | (财务大哥就继续去摸鱼了) commit; | ||
T5 | (就把事务A回滚了) rollback; | ||
T6 | ??? | ||
T7 |
| ??? |
就这样,因为 “脏读”就导致我每个月少1万大洋?
- 2. 不可重复读(针对其他提交前后,读取数据本身的对比)
事务A 先 查询了工资金额,是3000块钱,未提交 。事务B在事务A查询完之后,修改了工资金额,变成了13000, 在事务A前提交了;如果此时事务A再查询一次数据,就会发现钱跟上一次查询不一致,是13000,而不是3000。这就是不可重复读。强调事务A对要操作的数据被别人修改了,但在不知请的情况下拿去做之前的用途。
**场景同上:**老板嗷一嗓子,但没有指定哪个财务改。财务A大姐和财务B大哥都听到了,但他俩不知道由谁来改,就分别进行了下方流程操作:
时间点 | 事务A(财务大姐) | 事务B(财务大哥) |
T1 | (财务大姐要给我改,查到工资:3000) Begin; SELECT PAY from department where `NAME` = '陈哈哈'; | (财务大哥也要给我改,查到工资:3000) Begin; SELECT PAY from department where `NAME` = '陈哈哈'; |
T2 | (财务大姐喝了口水) | (财务大哥直接改了) UPDATE department SET ` PAY` = ` PAY` + 10000 where `NAME` = '陈哈哈'; |
T3 | (大姐正想改,突发强迫症,想再查一下,查到工资:13000) SELECT PAY from department where `NAME` = '陈哈哈'; |
|
T4 | (大姐:???) | (财务大哥就继续去摸鱼了) commit; |
T5 | (最终大姐回滚了,虽然并没有改什么,但你至少明白了什么是“强迫症”) rollback; | |
T6 | (大姐把BUG报给了产品部) | |
T7 | (一位35岁程序员被祭天) |
对于不可重复读,说简单点就是同一个事物内,查到的结果都不一致,就失去了MySQL的“一致性”,这是很严重的错误。你想,如果财务大姐没有二次确认,而是直接以第一次查询为准,又给我加了1万怎么办?想想还有点小激动呢。
- 3. 幻读(针对其他提交前后,读取数据条数的对比)
幻读是指在同一个事务中,存在前后两次查询同一个范围的数据,但是第二次查询却看到了第一次查询没看到的行,一般情况下只新增。
事务A先修改了某个表的所有纪录的状态字段为已处理,未提交;事务B也在此时新增了一条未处理的记录,并提交了;事务A随后查询记录,却发现有一条记录是未处理的,很是诧异,刚刚不是全部修改为已处理嘛,以为出现了幻觉,这就是幻读。
**场景:**老板每个月审批一次涨薪(审批表:shenpiTable),这时财务刚刚把我的工资申请提交了,老板正好在审批。一键审批通过后,突然看到了一条新的“未审批”记录(新增的),还是大侄子陈哈哈的。
老板:有幻觉?有BUG!!等等,我如果假装看不到这月是不是就省了1万块大洋?
陈哈哈:???
时间点 | 事务A(老板) | 事务B(财务大哥) |
T1 | (老板审批加工资申请,并没有注意到“陈哈哈”) Begin; SELECT * from shenpiTable where Status = '未通过'; | (财务大哥准备给我提交涨工资申请记录) Begin; SELECT * from shenpiTable where Status = '未通过'; |
T2 | (老板喝了口水) | (财务大哥摸了摸鱼) |
T3 | (老板一键通过了,头都没抬那种~) UPDATE shenpiTable SET Status = ‘通过’ where shenpiTable = ‘未通过’; | (财务大哥摸了摸鱼) |
T4 | (财务大哥新增一条我的申请记录) insert into shenpiTable values(“xxx”,"陈哈哈",“未通过”); | |
T5 | (老板又确认一下,突然发现我的审批还没通过。一脸懵逼,突然想到了什么,果断commit,笑嘻嘻的去洗脚了。) SELECT * from shenpiTable where Status = '未通过'; commit; | commit; |
T6 | ||
T7 | (第二天听到又有一位程序员被我打死) |
-
脏读说的是事务知道了自己本不应该知道的东西,强调的动作是查询,我看到了自己不该看的东西 ;
-
不可重复读强调的是一个人查的时候,其他人却可以增删改, 但我却不知道数据被改了,还拿去做了之前的用途;
-
幻读强调的是我修改了数据,等我要查的时候,却发现有我没有修改的记录,为什么,因为有其他人插了一条新的。
隔离级别概述
==================
为了解决上述问题,MySQL制定了四种不同的“隔离级别”,包括:读未提交(read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(serializable )。
隔离级别 | 效果 |
读未提交(RU) | 一个事务还没提交时,它做的变更就能被别的事务看到。(别的事务指同一时间进行的增删改查操作) |
读提交(RC) | 一个事务提交(commit)之后,它做的变更才会被其他事务看到。 |
可重复读(RR) | 一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。当然在可重复读隔离级别下,未提交变更对其他事务也是不可见的。 |
串行(xíng)化(S) | 正如物理书上写的,串行是单线路,顾名思义在MySQL中同一时刻只允许单个事务执行,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。 |
实例分析
================
(场景再现)老板:原来陈哈哈又双叒(ruò)叕(zhuó)是我失散多年的大侄子!财务,把 “陈哈哈” 的工资涨 “10000” !
以下表中的两个事务为例,看看在不同隔离级别下,分别会出现什么结果。能否避免上述问题呢?
时间点(宝强绿) | 事务A | 事务B |
T1 | Begin; SELECT PAY from department where `NAME` = '陈哈哈'; (查询工资:3000) | |
T2 | Begin; | |
T3 | SELECT PAY from department where `NAME` = '陈哈哈'; (查询工资:3000) | |
T4 | UPDATE department SET ` PAY` = `PAY` + 10000 where `NAME` = '陈哈哈'; | |
T5 | SELECT PAY from department where `NAME` = '陈哈哈'; (查询工资:Res_A1) | |
T6 | commit; | |
T7 | SELECT PAY from department where `NAME` = '陈哈哈'; (查询工资:Res_A2) | |
T8 | commit; | |
T9 | SELECT PAY from department where `NAME` = '陈哈哈'; (查询工资:Res_A3) |
- 读未提交(RU):
读未提交 | Res_A1 | Res_A2 | Res_A3 |
结果 | 13000 | 13000 | 13000 |
在RU隔离级别下,事务A 在T5时刻,就可以提前读到未提交的事务B 结果。
- 读提交(RC):
读提交 | Res_A1 | Res_A2 | Res_A3 |
结果 | 3000 | 13000 | 13000 |
读提交又叫读已提交,在RC隔离级别下,事务A 需要在 事务B commit提交后,才能看到事务B 修改的结果。所以在T5时刻,事务A 查到的陈哈哈的工资是 3000。
- 可重复读(RR)
可重复读 | Res_A1 | Res_A2 | Res_A3 |
结果 | 3000 | 3000 | 13000 |
可重复读是MySQL默认的隔离级别,在RR级别下,对于所有进行中(begin - commit)的事务,比如事务A,无论执行多少次SELECT(查询表 department ),只能看到的是同一张 department 表的结果视图(ReadView),该视图(ReadView)是在本事务启动(begin)时生成的,在事务A 结束(commit)后释放。该隔离级别会保证单事务内查看视图的一致性,称为“可重复读”。
- 串行(xíng)化(S)
串行化 | Res_A1 | Res_A2 | Res_A3 |
结果 | 3000 | 3000 | 13000 |
串行化隔离级别不支持并发事务,由于事务A 早于事务B,事务A执行SELECT时,就给 department 表加了锁,事务B 需要等事务A 结束后才能执行,因此T5、T7时刻是 3000,T8时刻事务A提交,事务B释放锁并执行,最后T9时刻查到我的工资是 13000。
原理描述
====
在实现上,数据库里面会创建一个视图,访问的时候以视图的逻辑结果为准。在MySQL默认的隔离级别“可重复读”隔离级别下,这个视图是在事务启动时创建的,整个事务存在期间都用这个视图。在“读提交”隔离级别下,这个视图是在每个 SQL 语句开始执行的时候创建的。这里需要注意的是,“读未提交”隔离级别下直接返回记录上的最新值,没有视图概念;而“串行化”隔离级别下直接用加锁的方式来避免并行访问。
我们可以看到在不同的隔离级别下,数据库行为是有所不同的。Oracle 数据库的默认隔离级别其实就是“读提交”,因此对于一些从 Oracle 迁移到 MySQL 的应用,为保证数据库隔离级别的一致,你一定要记得将 MySQL 的隔离级别设置为“读提交”。
配置的方式是,将启动参数 transaction-isolation 的值设置成 READ-COMMITTED。你可以用 show variables 来查看当前的值。
mysql> show variables like ‘transaction_isolation’;
±----------------------±---------------+
| Variable_name | Value |
±----------------------±---------------+
| transaction_isolation | READ-COMMITTED |
±----------------------±---------------+
小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Java工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Java开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
Docker步步实践
目录文档:
①Docker简介
②基本概念
③安装Docker
④使用镜像:
⑤操作容器:
⑥访问仓库:
⑦数据管理:
⑧使用网络:
⑨高级网络配置:
⑩安全:
⑪底层实现:
⑫其他项目:
图片转存中…(img-QWOtCcfE-1711020130504)]
⑧使用网络:
[外链图片转存中…(img-ptkIclw5-1711020130505)]
⑨高级网络配置:
[外链图片转存中…(img-XOZEYS0U-1711020130505)]
⑩安全:
[外链图片转存中…(img-fLFpNx6c-1711020130505)]
⑪底层实现:
[外链图片转存中…(img-tidGMoeh-1711020130506)]
⑫其他项目:
[外链图片转存中…(img-mXzLPXWf-1711020130506)]