10-MySQL-InnoDB-Transaction
背景描述
目前在维护的 APP 进行了整体重构(其实不能算重构,应该是重新开发),新的服务端系统根据业务场景进行了功能拆分,实现了微服务,结束了以前服务端大一统的部署模式;伴随着微服务的上线,分布式事务也成了一个绕不开的问题。最近的几篇文章计划结合目前项目中的分布式事务的代码实现逻辑,对数据库事务的知识进行系统的学习和整理,当然数据库事务方面的知识很多,所以肯定不是一篇文章可以搞定的,分布式 就是要 “分布试”。
PS.数据库事务的知识有哪些,一起来整理一下,今天先以 MySQL 数据库 InnoDB 存储引擎为例,总结一下单数据库事务。
事务的ACID特性
A:原子性(Atomicity)
事务被视为不可分割的最小单元,事务的所有操作要么全部提交成功,要么全部失败回滚。
C:一致性(Consistency)
一致性指事务将数据库从一种状态转变为下一种一致的状态。在事务开始之前和事务结束之后,数据库的完整性约束没有被破坏。在一致性状态下,所有事务对一个数据的读取结果都是相同的。
I:隔离性(Isolation)
一个事务所做的修改在最终提交以前,对其它事务是不可见的。
D:持久性(Durability)
一旦事务提交,则其所做的修改将会永远保存到数据库中。即使系统发生崩溃,事务的执行结果页不能丢失。
事务的并发一致性问题
丢失更新
第一类丢失更新
一个事务(A)更新某条记录数据,此时还未提交,这时另外一个事务(B)也更新了该记录数据并提交事务成功,这时 A 撤销事务,此时 B 更新成功的数据会丢失。
时间 | 事务A | 事务B |
---|---|---|
T1 | 开启事务 | |
T2 | 开启事务 | |
T3 | 查询账户余额为 100 | |
T4 | 查询账户余额为 100 | |
T5 | 更新数据 + 10 结果为 110 | |
T6 | 提交事务 | |
T7 | 更新数据 - 10 结果为 90 | |
T8 | 撤销事务 | |
T7 | 数据恢复为100(丢失更新) |
第二类丢失更新
一个事务(A)更新某条记录数据,此时还未提交,这时另外一个事务(B)也更新了该记录数据并提交事务成功,此时 A 提交事务,此时 B 更新成功的数据会丢失。
时间 | 事务A | 事务B |
---|---|---|
T1 | 开启事务 | |
T2 | 开启事务 | |
T3 | 查询账户余额为100 | |
T4 | 查询账户余额为100 | |
T5 | 更新数据 - 10 结果为 90 | |
T6 | 提交事务 | |
T7 | 更新数据 + 10 结果 为 110 | |
T8 | 提交事务 | |
T7 | 数据结果为110(丢失更新) |
脏读
脏读是指一个事务读取到了另外一个事务未提交的数据。一个事务正在对一条记录进行修改,在这个事务提交并完成前,这条记录的数据就处于不一致状态。这时, 另一个事务也来读取同一条记录,如果不加控制,第二个事务读取了这些“脏”数据,并据此做进一步的处理,就会产生未提交的数据依赖关系。
时间 | 事务A | 事务B |
---|---|---|
T1 | 开启事务 | |
T2 | 查询账户余额为100 | 开启事务 |
T3 | 充值50,余额修改为150 | |
T4 | 查询余额为150 | |
T5 | 撤销事务,余额改回100 | |
T6 | 汇入50,余额修改为200 | |
T7 | 提交事务 |
不可重复读
不可重复读是指一个事务读取到了另外一个事务已提交的数据。一个事务(A)读取某一个数据后,另外一个事务(B)对该数据进行了修改,当 A 再读取这个数据时,发现前后两次读取的数据不一致。
时间 | 事务A | 事务B |
---|---|---|
T1 | 开启事务 | |
T2 | 查询账户余额为100 | 开启事务 |
T3 | 更新账户余额为150 | |
T4 | 提交事务 | |
T5 | 查询账户余额为150 | |
T6 | … | |
T7 | 提交事务 |
幻读(幻影读)
一个事务(A)按某一条件检索到 N 条数据,另外一个事务(B)新增或删除了满足条件的数据,这时 A 再按相同条件检索数据,查询到的结果 != N。
时间 | 事务A | 事务B |
---|---|---|
T1 | 开启事务 | 开启事务 |
T2 | select * from table where condition = ‘xxx’ 返回 N 条记录 | |
T3 | 向 table 表插入一条满足 condition = ‘xxx’ 的数据 | |
T4 | 提交事务 | |
T5 | select * from table where condition = ‘xxx’ 返回 N + 1 条记录 | |
T6 | … | |
T7 | 提交事务 |
总结说明
幻读和不可重复读的区别:
- 不可重复读的重点是更新:在同一事务中,相同的条件,第一次和第二次读取到的数据不一致(中间有其它事务提交了更新);
- 幻读的重点是新增或删除:在同一事务中,相同的条件,第一次和第二次读到的记录数据不一样(中间有其它事务提交了新增或者删除)。
两类丢失更新问题:
- 第一类丢失更新 (通过设置 Repeatable Read 隔离级别解决)
- 第二类丢失更新 (需要应用程序控制,使用乐观锁解决)
事务隔离级别
SQL 标准定义了四种数据库事务的隔离级别,每一种级别中都规定了一个事务中所做的修改,哪些在事务内和事务间是可见的,哪些是不可见的。
- Read Uncommitted:所有事务都可以看到其它事务未提交的执行结果。
- Read Committed(RC):一个事务能够看到其它事务已提交的执行结果。
- Repeatable Read(RR):在一个事务内多次执行同一个查询操作,前后几次获取的结果相同。
- Serializable:串行化,每次读都需要获得表级共享锁,读写相互阻塞。
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
Read Uncommitted | YES | YES | YES |
Read Committed | NO | YES | YES |
Repeatable Read | NO | NO | YES |
Serializable | NO | NO | NO |
四种数据库事务的隔离级别只是 SQL 标准的定义,对于不同的数据库也会有不同的实现,比如:Oracle 仅支持 Read Committed 和 Serializable 隔离级别,其中 Read Committed 是默认的隔离级别;对于 MySQL 支持 SQL 标准的四种隔离级别,其中 Repeatable Read 为默认的隔离级别。
https://github.com/ctripcorp/apollo/wiki/Apollo%E5%BC%80%E6%94%BE%E5%B9%B3%E5%8F%B0
https://www.jianshu.com/p/03d1bf80f7e8
https://blog.csdn.net/z69183787/article/details/52213670
https://www.jianshu.com/p/592b2cdbc589
https://www.jianshu.com/p/d829df873332
https://blog.csdn.net/weixin_28760063/article/details/81369266
https://juejin.im/post/5b5a0bf9f265da0f6523913b