🈲🈲Ctrl + z
Ctrl + Y
🈲🈲
好不容易开开心心撸完这篇博客,一下子Ctrl Z,手忙脚乱,啪,没了😔😔。淦,这是二次回放。第一次的思维已经莫得了。。。谨慎撤销、谨慎撤销、谨慎撤销。
为什么需要事务?考虑一个场景——转账。账户A有100
块钱,给账户B转50
。其中就包含着两个有先后顺序的 update
操作。正常情况下,第一个update
,账户A 100-50
剩 50
。同时账户B update
加50
。总的金额还是100
,保持不变。如果出现异常,在第一次 update
执行结束后,第二个update
执行失败,那么账户A 100-50
剩 50
。但账户B并没有加50
。此时金额的总量由100
变成了50
。这是无法忍受的,那么就需要事务处理机制来解决。
一、事务
事务处理(transaction processing)可以用来维护数据库的完整性,它保证成批的MySQL操作要么完全执行,要么完全不执行。
用事务思维思考上述场景:如果转账的两个操作能够要么完全执行,要么完全不执行,还会出现金额丢失的情况吗?不会,结果要么正常转账,要么转账失败,但金额不变。这两个update就是事务。由此,可以进一步探究事务的特性👇👇。
ACID特性
四个特性ACID:
**原子性(Atomicity)**:事务的整个过程如原子操作一样,最终要么全部成功,或者全部失败。
**一致性(Consistency)**:一个事务必须使数据库从一个一致性状态变换到另一个一致性状态。即达到期望。
**隔离性(Isolation)**:一个事务的执行不能被其他事务干扰。
**持久性(Durability)**:一个事务一旦提交,他对数据库中数据的改变就应该是永久性的。当事务提交之后,数据会持久化到硬盘,修改是永久性的。
初步认识了事务的来源背景和事务特性,再来在看看数据库中怎么用,增强认识。
二、显隐式事务
隐式事务
MySQL
中事务默认是隐式事务,执行insert
、update
、delete
操作的时候,数据库自动开启事务、提交或回滚事务。
显式事务
很明显,显式事务就是需要手动开启、提交或回滚,由开发者自己控制。
三、事务操作
1. 操作术语
- 事务(transaction) 指一组SQL语句;
- 回退(rollback) 指撤销指定SQL语句的过程;
- 提交(commit) 指将未存储的SQL语句结果写入数据库表;
- 保留点(savepoint) 指事务处理中设置的临时占位符(placeholder),你可以对它发布回退(与回退整个事务处理不同)。
否开启隐式事务是由变量autocommit控制的。为ON表示开启了自动提交。
语法:
1 | show variables like 'transaction_isolation'; |
2. 开启事务
方式1:
1 | //设置不自动提交事务 |
方式2:
1 | // 开启事务 |
开启事务示例
1 | show variables like 'transaction_isolation'; |
3. 保留点
如果发生故障时不需要全部回退,而只选择性的回退一部分,则可以使用保留点,回退到指定的点。
1 | SAVEPOINT 保留点名; |
4. 只读事务
1 | start transaction read only; |
开启了之后,执行更改数据的命令会出错。
进一步解析事务执行
1 | // 开启手动操作事务 |
可见,在事务中一旦执行更新表操作,表中的数据就会变化。而此时其他事务是可以读取表中的数据来使用的,而一旦该事务不提交而回滚了,那么其他事务读取到的将是错误的数据。由此,说明事务是存在问题的。
四、事务中存在的问题
- 脏读
一个事务在执行的过程中读取到了其他事务还没有提交的数据。
- 读已提交
一个事务操作过程中可以读取到其他事务已经提交的数据。
事务中的每次读取操作,读取到的都是数据库中其他事务已提交的最新的数据(相当于当前读)
- 可重复读
一个事务操作中对于一个读取操作不管多少次,读取到的结果都是一样的。
- 幻读
幻读发生在可重复读的基础上。事务中后面的操作需要前面的读取操作提供支持,但读取操作却不能支持下面的操作时产生的错误,就像发生了幻觉一样。
比如:
事务A:1.读取某条记录—发现不存在;2.那么事务A进行插入—结果插入失败;3.再次读取该条记录—还是发现不存在——事务A纳闷发生了什么?(幻读-事务A明明没有读到该条记录啊)
事务B:1.在事务A读取之后,插入之前,事务B插入了该条数据——导致事务A插入失败;2.但事务A再次读的时候还是不存在(可重复读)
针对上面这些问题:如何保证事务中数据的正确性——一个事务是否可以看见另一个事务已提交或未提交的数据?——通过事务的隔离级别来解决。
五、隔离级别
事务隔离级别主要是解决了上面多个事务之间数据可见性及数据正确性的问题。
隔离级别相当于数据库环境的状态,不同的状态有不同的功能效果。
隔离级别分为4种:
读未提交:READ-UNCOMMITTED
读已提交:READ-COMMITTED
可重复读:REPEATABLE-READ
串行:SERIALIZABLE
4中隔离级别越来越强,会导致数据库的并发性也越来越低。
查看隔离级别:show variables like ‘transaction_isolation’;
设置隔离级别
- 修改mysql中的my.init文件
1 | # 隔离级别设置,READ-UNCOMMITTED读未提交, READ-COMMITTED读已提交, |
- 重启mysql。
各种隔离级别中会出现的问题
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
READ-UNCOMMITTED | 有 | 有 | 无 |
READ-COMMITTED | 无 | 有 | 无 |
REPEATABLE-READ | 无 | 无 | 有 |
SERIALIZABLE 无 | 无 | 无 | 无 |
1. 隔离级别为——读未提交
场景模拟(空表,两个事务,按序号顺序):
事务A:1. 开启事务,然后读取——空表;4.读取——成功,事务B插入的数据;6.commit;
事务B:2.开启事务,然后插入数据——成功;3.读取——成功,有数据;5. commit;
结论:
- 读未提交情况下,可以读取到其他事务还未提交的数据,出现了脏读;
- 多次读取结果不一样,为不可重复读。
2. 隔离级别为——读已提交
场景模拟(空表,两个事务,按序号顺序):
事务A:1.开启事务,然后读取——空表;4. 读取数据——还是空表;6. 读取数据——有事务B插入的数据;7.commit;
事务B:2.开启事务,然后插入——成功;3.读取数据——成功;5. commit;
结论:
- 读已提交情况下,无法读取到其他事务还未提交的数据,未出现脏读;
- 可以读取到其他事务已经提交的数据,出现了读已提交;
- 多次读取结果不一样,不可重复读。
3. 隔离级别为——可重复读
场景模拟(空表,两个事务,按序号顺序):
事务A:1.开启事务,然后读取——空表;3.读取数据——还是空表;5.读取数据——还是空表;6.commit;7. 读取数据——有事务B插入的数据;
事务B:1.开启事务,插入数据——成功;2.读取数据——成功;4.commit;
结论:
• 可重复读的情况下,无法读取到其他事务还未提交的数据,未出现脏读;
• 未读取到其他事务已经提交的数据,且多次读取结果一直,为可重复读;
4. 隔离级别为——可重复读——幻读
场景模拟(空表,两个事务,按序号顺序):
事务A:1.开启事务,然后读取某条记录——发现不存在;4.进行该记录插入——结果插入失败;5.再次读取该条记录——还是发现不存在啊——事务A纳闷发生了什么?第一次读取是发生了幻觉?(幻读-事务A明明没有读到该条记录啊)
事务B:2.开启事务,插入了该条数据——成功;3.读取该条记录——成功;6.commit;
结论:
事务A第一次读取的时候相当于幻觉,即幻读。明明没这条数据,但插入还是失败,第二次再读取来确认,发现确实是不存在的呀,晕。——第二次读还是不存在,得益于可重复读级别。
5. 隔离级别为——串行
串行是针对整个事务而言的。一个事务想要执行,必须等待上一个事务执行结束才可以执行。
结论:一个个事务串行执行,不存在脏读、不可重复读、幻读的问题。但效率较低。
六、关于隔离级别的选择
- 需要对各种隔离级别产生的现象非常了解,然后选择的时候才能游刃有余
- 隔离级别越高,并发性也低,比如最高级别SERIALIZABLE 会让事物串行执行,并发操作变成串行了,会导致系统性能直接降低。
- 具体选择哪种需要结合具体的业务来选择。
- 读已提交(READ-COMMITTED)通常用的比较多。
吐槽:好好的一篇文章变成了流水账,可惜!!!💔💔写文章确实需要灵感,一件艺术品,总是需要精心雕刻的。
参考
- 《MySQL必知必会》
- 玩转Mysql系列 - 第13篇:详解事务