本文主要详细介绍MySQL的四个事务隔离级别的相关信息。本文中的示例代码非常详细,具有一定的参考价值。感兴趣的朋友可以参考一下。
本文的测试环境:Windows 10 cmd MySQL5.6.36 InnoDB
一、事务的基本要素(ACID)
1.原子性:事务开始后,所有操作要么完成,要么根本不做,不可能中途停滞。如果事务执行中出现错误,将回滚到事务开始前的状态,所有操作将如同没有发生一样。也就是说,商业是一个不可分割的整体,就像化学中的原子一样,是构成物质的基本单位。
2.一致性:在事务开始之前和之后,数据库的完整性约束不会被破坏。比如A给B转账,A不可能扣款,B却不收。
3.隔离:同时只允许一个事务请求相同的数据,不同事务之间不存在干扰。举个例子,A正在从一张银行卡上取钱,B在A的取钱流程结束之前,无法向这张卡转账。
4.持久性:事务完成后,事务对数据库的所有更新都将保存到数据库中,并且不能回滚。
总结:原子性是事务隔离的基础,隔离和持久化是手段,最终目的是保持数据一致。
二、事务的并发问题
1.脏读:事务a读事务b更新的数据,然后b回滚,所以事务a读的数据是脏数据。
2.不可重复读取:事务A多次读取同一数据,而事务B在事务A的多次读取过程中更新并提交数据,导致事务A多次读取同一数据时结果不一致。
3.魔读:系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE成绩,但系统管理员B此时插入了具体分数的记录。当系统管理员A完成修改后,发现还有一条记录没有被修改,好像产生了幻觉。这就是所谓的魔法阅读。
总结:不可重复读数和幻像读数很容易混淆。不可重复读数侧重于修改,而幻像读数侧重于添加或删除。解决不可重复读取的问题只需要锁定满足条件的行,解决幻影读取的问题需要锁定表。
四、用例子说明各个隔离级别的情况
1.未提交的读取:
(1)打开客户端a,设置当前交易模式为读取未提交,查询表账户初始值:
(2)在客户端A的交易提交之前,打开另一个客户端B并更新表帐户:
(3)此时,虽然客户端B的交易尚未提交,但是客户端A可以查询客户端B的更新数据:
(4)一旦客户端B的事务因为某种原因回滚,所有操作都会被取消,客户端A查询的数据实际上是脏数据:
(5)在客户端A上执行update语句update account set balance=balance-50,其中id=1,李雷的余额是400而不是350。你没有问数据的一致性是不是很奇怪?如果你这么想,那就太天真了。在应用程序中,我们将使用400-50=350,我们不知道其他会话回滚。为了解决这个问题,
2.阅读已提交。
(1)打开客户端a,设置当前交易模式为读提交,查询表账户初始值:
(2)在客户端A的交易提交之前,打开另一个客户端B并更新表帐户:
(3)此时客户端B的交易还没有提交,客户端A无法查询B的更新数据,从而解决了脏读问题:
(4)客户b的交易提交
(5)客户端A执行与上一步相同的查询,结果与上一步不一致,即不重复问题。在应用程序中,假设我们在客户端A的会话中,李雷的余额是450,但是其他事务将李雷的余额值更改为400。我们不知道如果用450的值做其他运算会不会有问题,但是这个概率真的很小。
3、可以反复阅读
(1)打开客户端a,设置当前交易模式为可重复读取,查询表账户初始值:
(2)在客户端A的交易提交之前,打开另一个客户端B,更新表账户并提交。客户端B的事务实际上可以修改客户端A的事务查询的行,即mysql的可重复读取不会锁定事务查询的行。这出乎我的意料。当sql标准中的事务隔离级别是可重复读取时,读写操作必须锁定行,但mysql没有锁。我去了那里。注意锁定应用程序中的行,否则会将步骤(1)中李雷的余额作为400作为中间值来进行其他操作。
(3)在客户端A执行步骤(1)的查询:
(4)执行步骤(1),李雷余额仍为400,与步骤(1)的查询结果一致,不存在不重复的问题;然后更新balance=balance-50其中ID=1,balance不会变成400-50=350。李雷的余额值在步骤(2)中用350计算,所以是300,但是数据的一致性没有被破坏。这个有点神奇,可能是mysql的特点吧。
mysql select * from account
- - -
| id |姓名|余额|
- - -
| 1 |李雷| 400 |
| 2 |韩梅| 16000 |人
| 3 |露西| 2400 |
- - -
集合中的行(0.00秒)
mysql更新账套balance=balance - 50其中id=1;
查询正常,1行受影响(0.00秒)
匹配的行数:1已更改:1警告数:0
mysql select * from account
- - -
| id |姓名|余额|
- - -
1 |李雷| 300 |
| 2 |韩梅| 16000 |人
| 3 |露西| 2400 |
- - -
集合中的行(0.00秒)
(5)在客户端A启动交易,查询表账户的初始值。
mysql启动事务;
查询正常,0行受影响(0.00秒)
mysql select * from account
- - -
| id |姓名|余额|
- - -
1 |李雷| 300 |
| 2 |韩梅| 16000 |人
| 3 |露西| 2400 |
- - -
集合中的行(0.00秒)
(6)在客户端B启动交易,添加一条新数据,其中余额字段的值为600,并提交。
mysql启动事务;
查询正常,0行受影响(0.00秒)
mysql插入帐户值(4,' lily ',600);
查询正常,1行受影响(0.00秒)
mysql提交;
查询正常,0行受影响(0.01秒)
(7)计算客户端A中余额的总和,值为300 16000 2400=18700。如果不包括客户端B的值,那么计算客户端A提交后的余额之和,实际上变成了19300。这是因为包含了客户端B的600。站在客户的角度,客户看不到客户端B,会觉得大饼丢了人间,还有600多个。但是,在应用程序中,我们的代码可能会向用户提交18700。如果一定要避免这种小概率的发生,应该采用下面要介绍的事务隔离级别“序列化”。
MySQL select sum(balance)from account;
-
总和(余额)
-
| 18700 |
-
集合中的1行(0.00秒)
mysql提交;
查询正常,0行受影响(0.00秒)
MySQL select sum(balance)from account;
-
总和(余额)
-
| 19300 |
-
集合中的1行(0.00秒)
4.序列化
(1)打开客户端A,设置当前交易模式为可序列化,查询表账户初始值:
mysql设置可序列化的会话事务隔离级别;
查询正常,0行受影响(0.00秒)
mysql启动事务;
查询正常,0行受影响(0.00秒)
mysql select * from account
- - -
| id |姓名|余额|
- - -
1 |李雷| 10000 |
| 2 |韩梅| 10000 |人
| 3 |露西| 10000 |
| 4 |百合| 10000 |
- - -
集合中的行(0.00秒)
(2)打开a客户端B,设置当前事务模式为可序列化,插入一条记录并报错,表被锁定。插入失败。当mysql中的事务隔离级别为serializable时,表将被锁定,因此不会有神奇的读取。这种隔离级别在并发性上是极低的,往往一个事务占用一个表,而其他几千个事务只能干瞪眼。开发中很少用到。
mysql设置可序列化的会话事务隔离级别;
查询正常,0行受影响(0.00秒)
mysql启动事务;
查询正常,0行受影响(0.00秒)
mysql插入帐户值(5,' tom ',0);
错误1205 (HY000):超过锁定等待超时;尝试重新启动交易
补充:
1.根据SQL规范中规定的标准,不同数据库的具体实现可能会有一些差异。
2.mysql中默认的事务隔离级别是可重复的,读取的行不会被锁定。
3.当事务隔离级别为序列化时,读取数据将锁定整个表。
4.看这篇文章的时候,站在开发者的角度,你可能会觉得不能反复读,读的很神奇。逻辑上没有问题,最后的数据还是一致的。但是从用户的角度来看,他们只能看到一个事务(只能看到客户端A,不知道客户端B的卧底存在),没有考虑事务并发执行的现象。一旦相同的数据被多次读取而得到不同的结果,或者凭空出现新的记录,它们可能会生成。
5.在mysql中执行一个事务时,最终结果不会存在数据一致性的问题,因为在一个事务中,mysql在执行一个操作时可能不会使用前一个操作的中间结果,但会根据其他并发事务的实际情况进行处理。看似不合逻辑,却保证了数据的一致性;但是,当在应用程序中执行事务时,一个操作的结果将被下一个操作使用,并进行其他计算。这是我们必须小心的。重复读取时要锁行,序列化时要锁表,否则会破坏数据的一致性。
6.在mysql中执行一个事务时,mysql会根据每个事务的实际情况综合处理,使得数据一致性不被破坏。但是在应用一个应用的时候不像mysql那样按照逻辑套路出牌,不可避免的会出现数据一致性问题。
7.隔离级别越高,数据完整性和一致性越好,但对并发性能的影响也越大。你不能鱼与熊掌兼得。对于大多数应用来说,可以将数据库系统的隔离级别设置为先提交读,这样可以避免脏读,具有良好的并发性能。虽然会导致不可重复读取、幻影读取等一些并发问题,但应用可以在个别场合采用悲观锁或乐观锁来控制这些问题。
这就是本文的全部内容。希望对大家的学习有帮助,支持我们。
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。