上个厕所的功夫,搞懂MySQL事务隔离级别,Java学习视频百度云盘

本文深入探讨了数据库并发环境下可能出现的脏读、不可重复读和幻读问题,并通过实例分析了MySQL的四种隔离级别:读未提交、读提交、可重复读和串行化如何应对这些问题。在可重复读隔离级别下,事务看到的数据视图保持一致,而在读提交和串行化级别下,数据一致性得到不同级别的保障。了解这些概念对于优化数据库事务处理和确保数据准确性至关重要。
摘要由CSDN通过智能技术生成

| 14 | 朱志鹏 | 男 | 25 | 技术1部 | 5000 | 看小说 |

| 19 | 李昂 | 男 | 27 | 技术1部 | 7000 | 看片儿 |

±—±----------±----±----±-----------±-----±----------+

8 rows in set (0.00 sec)




**为了更好理解下文,这里先给出个业务场景:**



> 老板:原来陈哈哈是我失散多年的大侄子!财务,给 “陈哈哈” 的工资涨 “10000” 大洋。

> 

> 陈哈哈:谢谢老叔!MUA~



并发场景下事务存在的数据问题

==============



下面我们介绍一下脏读(dirty read)、不可重复读(non-repeatable read)、幻读(phantom read)这三类并发问题,以及每种问题出现的原理及场景。



*   **1\. 脏读(针对的是未提交读数据)**

    ----------------------

    



事务A修改了数据,但未提交,而事务B查询了事务A修改过却没有提交的数据,这就是脏读,因为事务A可能会回滚。



> **场景:**老板(老叔)大喊了一嗓子,但没有指定哪个财务改。财务A大姐和财务B大哥都听到了,但他俩不知道由谁来改,就分别进行了下方流程操作:



<table align="center" border="1" cellpadding="1" cellspacing="1"><tbody><tr><td style="text-align:center;width:90px;">时间点</td><td style="text-align:center;width:305px;">事务A(财务大姐)</td><td style="text-align:center;width:398px;">事务B(财务大哥)</td><td style="text-align:center;width:105px;">陈哈哈</td></tr><tr><td style="text-align:center;width:90px;">T1</td><td style="width:305px;"><p>(财务大姐要给我改,查到工资:3000)</p><p>Begin;</p><p>SELECT PAY from department where `NAME` = '陈哈哈';</p></td><td style="width:398px;"><p>&nbsp;</p></td><td style="width:105px;">&nbsp;</td></tr><tr><td style="text-align:center;width:90px;">T2</td><td style="width:305px;">UPDATE department SET ` PAY` = ` PAY` + 10000 where `NAME` = '陈哈哈';</td><td style="width:398px;"><p>(财务大哥看看财务大姐改没改)</p><p>Begin;</p></td><td style="width:105px;">&nbsp;</td></tr><tr><td style="text-align:center;width:90px;">T3</td><td style="width:305px;">(财务大姐突然发现财务大哥也在给我改)</td><td style="width:398px;"><p>(一查发现。哦!已经改了)(工资:13000)</p><p>SELECT PAY from department where `NAME` = '陈哈哈';</p></td><td style="width:105px;">&nbsp;</td></tr><tr><td style="text-align:center;width:90px;">T4</td><td style="width:305px;">&nbsp;</td><td style="width:398px;"><p>(财务大哥就继续去摸鱼了)</p><p>commit;</p></td><td style="width:105px;">&nbsp;</td></tr><tr><td style="text-align:center;width:90px;">T5</td><td style="width:305px;"><p>(就把事务A回滚了)</p><p>rollback;</p></td><td style="width:398px;">&nbsp;</td><td style="width:105px;">&nbsp;</td></tr><tr><td style="text-align:center;width:90px;">T6</td><td style="width:305px;">&nbsp;</td><td style="width:398px;">&nbsp;</td><td style="width:105px;">???</td></tr><tr><td style="text-align:center;width:90px;">T7</td><td style="width:305px;"><p>&nbsp;</p></td><td style="width:398px;">&nbsp;</td><td style="width:105px;">???</td></tr></tbody></table>



就这样,因为 “脏读”就导致我每个月少1万大洋?



![æ è¯­_æ è¯­è¡¨æ](https://imgconvert.csdnimg.cn/aHR0cDovL3d3NC5zaW5haW1nLmNuL2xhcmdlLzZhZjg5YmM4Z3cxZjhxcGJxd2hhZGoyMDJrMDJ1M3k5LmpwZw?x-oss-process=image/format,png)



*   **2\. 不可重复读(针对其他提交前后,读取数据本身的对比)**

    ---------------------------------

    



事务A 先 查询了工资金额,是3000块钱,未提交 。事务B在事务A查询完之后,修改了工资金额,变成了13000, 在事务A前提交了;如果此时事务A再查询一次数据,就会发现钱跟上一次查询不一致,是13000,而不是3000。这就是**不可重复读**。强调事务A对要操作的数据被别人修改了,但在不知请的情况下拿去做之前的用途。



> **场景同上:**老板嗷一嗓子,但没有指定哪个财务改。财务A大姐和财务B大哥都听到了,但他俩不知道由谁来改,就分别进行了下方流程操作:



<table align="center" border="1" cellpadding="1" cellspacing="1"><tbody><tr><td style="text-align:center;width:76px;">时间点</td><td style="text-align:center;width:389px;">事务A(财务大姐)</td><td style="text-align:center;width:327px;">事务B(财务大哥)</td></tr><tr><td style="text-align:center;width:76px;">T1</td><td style="width:389px;"><p>(财务大姐要给我改,查到工资:3000)</p><p>Begin;</p><p>SELECT PAY from department where `NAME` = '陈哈哈';</p></td><td style="width:327px;"><p>(财务大哥也要给我改,查到工资:3000)</p><p>Begin;</p><p>SELECT PAY from department where `NAME` = '陈哈哈';</p></td></tr><tr><td style="text-align:center;width:76px;">T2</td><td style="width:389px;">(财务大姐喝了口水)</td><td style="width:327px;"><p>(财务大哥直接改了)</p><p>UPDATE department SET ` PAY` = ` PAY` + 10000 where `NAME` = '陈哈哈';</p></td></tr><tr><td style="text-align:center;width:76px;">T3</td><td style="width:389px;"><p>(大姐正想改,突发强迫症,想再查一下,查到工资:13000)</p><p>SELECT PAY from department where `NAME` = '陈哈哈';</p></td><td style="width:327px;"><p>&nbsp;</p></td></tr><tr><td style="text-align:center;width:76px;">T4</td><td style="width:389px;">(大姐:???)</td><td style="width:327px;"><p>(财务大哥就继续去摸鱼了)</p><p>commit;</p></td></tr><tr><td style="text-align:center;width:76px;">T5</td><td style="width:389px;"><p>(最终大姐回滚了,虽然并没有改什么,但你至少明白了什么是“强迫症”)</p><p>rollback;</p></td><td style="width:327px;">&nbsp;</td></tr><tr><td style="text-align:center;width:76px;">T6</td><td style="width:389px;">(大姐把BUG报给了产品部)</td><td style="width:327px;">&nbsp;</td></tr><tr><td style="text-align:center;width:76px;">T7</td><td style="width:389px;"><p>(一位35岁程序员被祭天)</p></td><td style="width:327px;">&nbsp;</td></tr></tbody></table>



![](https://imgconvert.csdnimg.cn/aHR0cDovL3d4NC5zaW5haW1nLmNuL2xhcmdlLzAwNnN6dkxGZ3kxZndsbDhmcHl6ZGozMGI0MDdkcTVkLmpwZw?x-oss-process=image/format,png)



对于不可重复读,说简单点就是同一个事物内,查到的结果都不一致,就失去了MySQL的“一致性”,这是很严重的错误。你想,如果财务大姐没有二次确认,而是直接以第一次查询为准,又给我加了1万怎么办?想想还有点小激动呢。



*   **3\. 幻读(针对其他提交前后,读取数据条数的对比)**

    ------------------------------

    



幻读是指在同一个事务中,存在前后两次查询同一个范围的数据,但是第二次查询却看到了第一次查询没看到的行,一般情况下只新增。



事务A先修改了某个表的所有纪录的状态字段为已处理,未提交;事务B也在此时新增了一条未处理的记录,并提交了;事务A随后查询记录,却发现有一条记录是未处理的,很是诧异,刚刚不是全部修改为已处理嘛,以为出现了幻觉,这就是幻读。



> **场景:**老板每个月审批一次涨薪(审批表:shenpiTable),这时财务刚刚把我的工资申请提交了,老板正好在审批。一键审批通过后,突然看到了一条新的“未审批”记录(新增的),还是大侄子陈哈哈的。

> 

> 老板:有幻觉?有BUG!!等等,我如果假装看不到这月是不是就省了1万块大洋?

> 

> 陈哈哈:???



<table align="center" border="1" cellpadding="1" cellspacing="1"><tbody><tr><td style="text-align:center;width:76px;">时间点</td><td style="text-align:center;width:389px;">事务A(老板)</td><td style="text-align:center;width:327px;">事务B(财务大哥)</td></tr><tr><td style="text-align:center;width:76px;">T1</td><td style="width:389px;"><p>(老板审批加工资申请,并没有注意到“陈哈哈”)</p><p>Begin;</p><p>SELECT *&nbsp;from shenpiTable&nbsp;where Status = '未通过';</p></td><td style="width:327px;"><p>(财务大哥准备给我提交涨工资申请记录)</p><p>Begin;</p><p>SELECT *&nbsp;from shenpiTable&nbsp;where Status = '未通过';</p></td></tr><tr><td style="text-align:center;width:76px;">T2</td><td style="width:389px;">(老板喝了口水)</td><td style="width:327px;"><p>(财务大哥摸了摸鱼)</p></td></tr><tr><td style="text-align:center;width:76px;">T3</td><td style="width:389px;"><p>(老板一键通过了,头都没抬那种~)</p><p>UPDATE shenpiTable&nbsp;SET&nbsp; Status = ‘通过’ where shenpiTable&nbsp;= ‘未通过’;</p></td><td style="width:327px;"><p>(财务大哥摸了摸鱼)</p></td></tr><tr><td style="text-align:center;width:76px;">T4</td><td style="width:389px;">&nbsp;</td><td style="width:327px;"><p>(财务大哥新增一条我的申请记录)</p><p>insert into&nbsp;shenpiTable values(“xxx”,"陈哈哈",“未通过”);</p></td></tr><tr><td style="text-align:center;width:76px;">T5</td><td style="width:389px;"><p>(老板又确认一下,突然发现我的审批还没通过。一脸懵逼,突然想到了什么,果断commit,笑嘻嘻的去洗脚了。)</p><p>SELECT *&nbsp;from shenpiTable&nbsp;where Status = '未通过';</p><p>commit;</p></td><td style="width:327px;">commit;</td></tr><tr><td style="text-align:center;width:76px;">T6</td><td style="width:389px;">&nbsp;</td><td style="width:327px;">&nbsp;</td></tr><tr><td style="text-align:center;width:76px;">T7</td><td style="width:389px;"><p>(第二天听到又有一位程序员被我打死)</p></td><td style="width:327px;">&nbsp;</td></tr></tbody></table>



*   **脏读说的是事务知道了自己本不应该知道的东西,强调的动作是查询,我看到了自己不该看的东西 ;**

*   **不可重复读强调的是一个人查的时候,其他人却可以增删改, 但我却不知道数据被改了,还拿去做了之前的用途;**

*   **幻读强调的是我修改了数据,等我要查的时候,却发现有我没有修改的记录,为什么,因为有其他人插了一条新的。**



  

 



******隔离级别概述******

==================



为了解决上述问题,MySQL制定了四种不同的“隔离级别”,包括:读未提交(read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(serializable )。



<table border="1" cellpadding="1" cellspacing="1"><tbody><tr><td style="width:163px;">隔离级别</td><td style="width:635px;">效果</td></tr><tr><td style="width:163px;"><strong>读未提交(RU)</strong></td><td style="width:635px;">一个事务还没提交时,它做的变更就能被别的事务看到。(别的事务指同一时间进行的增删改查操作)</td></tr><tr><td style="width:163px;"><strong>读提交(RC)</strong></td><td style="width:635px;">一个事务提交(commit)之后,它做的变更才会被其他事务看到。</td></tr><tr><td style="width:163px;"><strong>可重复读(RR)</strong></td><td style="width:635px;">一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。当然在可重复读隔离级别下,未提交变更对其他事务也是不可见的。</td></tr><tr><td style="width:163px;"><strong>串行(xíng)化(S)</strong></td><td style="width:635px;">正如物理书上写的,串行是单线路,顾名思义在MySQL中同一时刻只允许单个事务执行,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。</td></tr></tbody></table>



******实例分析******

================



> (场景再现)老板:原来陈哈哈又双叒(ruò)叕(zhuó)是我失散多年的大侄子!财务,把 “陈哈哈” 的工资涨 “10000” !



以下表中的两个事务为例,看看在不同隔离级别下,分别会出现什么结果。能否避免上述问题呢?



<table align="center" border="1" cellpadding="1" cellspacing="1"><tbody><tr><td style="text-align:center;">时间点(宝强绿)</td><td style="text-align:center;width:401px;">事务A</td><td style="text-align:center;width:364px;">事务B</td></tr><tr><td style="text-align:center;">T1</td><td style="width:401px;"><p>Begin;</p><p>SELECT PAY from department where `NAME` = '陈哈哈';</p><p>(查询工资:3000)</p></td><td style="width:364px;">&nbsp;</td></tr><tr><td style="text-align:center;">T2</td><td style="width:401px;">&nbsp;</td><td style="width:364px;">Begin;</td></tr><tr><td style="text-align:center;">T3</td><td style="width:401px;">&nbsp;</td><td style="width:364px;"><p>SELECT PAY from department where `NAME` = '陈哈哈';</p><p>(查询工资:3000)</p></td></tr><tr><td style="text-align:center;">T4</td><td style="width:401px;">&nbsp;</td><td style="width:364px;">UPDATE department SET ` PAY` = `PAY` + 10000 where `NAME` = '陈哈哈';</td></tr><tr><td style="text-align:center;">T5</td><td style="width:401px;"><p>SELECT PAY from department where `NAME` = '陈哈哈';</p><p>(查询工资:Res_A1)</p></td><td style="width:364px;">&nbsp;</td></tr><tr><td style="text-align:center;">T6</td><td style="width:401px;">&nbsp;</td><td style="width:364px;">commit;</td></tr><tr><td style="text-align:center;">T7</td><td style="width:401px;"><p>SELECT PAY from department where `NAME` = '陈哈哈';</p><p>(查询工资:Res_A2)</p></td><td style="width:364px;">&nbsp;</td></tr><tr><td style="text-align:center;">T8</td><td style="width:401px;">commit;</td><td style="width:364px;">&nbsp;</td></tr><tr><td style="text-align:center;">T9</td><td style="width:401px;"><p>SELECT PAY from department where `NAME` = '陈哈哈';</p><p>(查询工资:Res_A3)</p></td><td style="width:364px;">&nbsp;</td></tr></tbody></table>



*   **读未提交(RU):**

    -------------

    



<table border="1" cellpadding="1" cellspacing="1"><tbody><tr><td style="width:134px;"><strong>读未提交</strong></td><td style="width:268px;">Res_A1</td><td>Res_A2</td><td>Res_A3</td></tr><tr><td style="width:134px;">结果</td><td style="width:268px;">13000</td><td>13000</td><td>13000</td></tr></tbody></table>



在RU隔离级别下,事务A 在T5时刻,就可以提前读到未提交的事务B 结果。



*   **读提交(RC):**

    ------------

    



<table border="1" cellpadding="1" cellspacing="1"><tbody><tr><td style="width:134px;"><strong>读提交</strong></td><td style="width:268px;">Res_A1</td><td>Res_A2</td><td>Res_A3</td></tr><tr><td style="width:134px;">结果</td><td style="width:268px;">3000</td><td>13000</td><td>13000</td></tr></tbody></table>



读提交又叫读已提交,在RC隔离级别下,事务A 需要在 事务B commit提交后,才能看到事务B 修改的结果。所以在T5时刻,事务A 查到的陈哈哈的工资是 3000。



*   **可重复读(RR)**

    ------------

    



<table border="1" cellpadding="1" cellspacing="1"><tbody><tr><td style="width:134px;"><strong>可重复读</strong></td><td style="width:268px;">Res_A1</td><td>Res_A2</td><td>Res_A3</td></tr><tr><td style="width:134px;">结果</td><td style="width:268px;">3000</td><td>3000</td><td>13000</td></tr></tbody></table>



可重复读是MySQL默认的隔离级别,在RR级别下,对于所有进行中(begin - commit)的事务,比如事务A,无论执行多少次SELECT(查询表 department ),只能看到的是同一张 department 表的结果视图(ReadView),该视图(ReadView)是在本事务启动(begin)时生成的,在事务A 结束(commit)后释放。该隔离级别会保证单事务内查看视图的一致性,称为“可重复读”。



*   **串行(xíng)化(S)**

    ----------------

    



<table border="1" cellpadding="1" cellspacing="1"><tbody><tr><td style="width:134px;"><strong>串行化</strong></td><td style="width:268px;">Res_A1</td><td>Res_A2</td><td>Res_A3</td></tr><tr><td style="width:134px;">结果</td><td style="width:268px;">3000</td><td>3000</td><td>13000</td></tr></tbody></table>



串行化隔离级别不支持并发事务,由于事务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 |

±----------------------±---------------+




总结来说,存在即合理,每种隔离级别都有自己的使用场景,你要根据自己的业务情况来定。我想你可能会问那什么时候需要“可重复读”的场景呢?我们来看一个数据校对逻辑的案例。



假设你在管理一个个人银行账户表。一个表存了每个月月底的余额,一个表存了账单明细。这时候你要做数据校对,也就是判断上个月的余额和当前余额的差额,是否与本月的账单明细一致。你一定希望在校对过程中,即使有用户发生了一笔新的交易,也不影响你的校对结果。



这时候使用“可重复读”隔离级别就很方便。事务启动时的视图可以认为是静态的,不受其他事务更新的影响。



四种隔离级别的问题解决情况

=============



| 标题 | 脏读 | 不可重复读 | 幻读 |

| --- | --- | --- | --- |

| **读未提交(RU)** | × | × | × |

| **读提交(RC)** | √ | × | × |

| **可重复读(RR)** | √ | √ | **√** |

| **串行(xíng)化(S)** | √ | √ | √ |



测试建表语句

======



独家面经总结,超级精彩

本人面试腾讯,阿里,百度等企业总结下来的面试经历,都是真实的,分享给大家!

image

image

image

image

Java面试准备

准确的说这里又分为两部分:

  1. Java刷题
  2. 算法刷题

Java刷题:此份文档详细记录了千道面试题与详解;

image

image

中…(img-dZCNw2XG-1631091485210)]

[外链图片转存中…(img-F5qUjh8I-1631091485212)]

[外链图片转存中…(img-8uvDAdZM-1631091485213)]

[外链图片转存中…(img-FwDXo9wE-1631091485215)]

Java面试准备

准确的说这里又分为两部分:

  1. Java刷题
  2. 算法刷题

Java刷题:此份文档详细记录了千道面试题与详解;

[外链图片转存中…(img-x9Svyin7-1631091485216)]

[外链图片转存中…(img-EeHvyZXr-1631091485217)]

CodeChina开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频】

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值