NOTE
数据库事务
1. 什么是事务 将多个读写操作合并成一个逻辑单元,视作一个操作执行 这个操作满足ACID特性 2. 为什么需要事务 主要是为了让应用层简化对一系列问题的处理 事务是一个抽象层,应用程序 事务 数据库。 通过事务的抽象应用程序可以假装数据库不会崩溃(原子性),没有其他人同时访问数据库(隔离),存储设备是完全可靠的(持久性) 所有这些错误都被简化为 事务中止 ,而应用需要的仅仅是重试 3. 事务的特性 3.1. A (原子性) 3.1.1. 原子性是什么 无论有多少条sql语句,要么整体执行失败,要么整体执行成功。 原子性 这个的原子性不是指并发的,即不是描述多个线程同时访问相同的数据会发生什么
这是历史学习笔记,可能存在过时或不完整的理解。
1. 什么是事务
- 将多个读写操作合并成一个逻辑单元,视作一个操作执行
- 这个操作满足ACID特性
2. 为什么需要事务
- 主要是为了让应用层简化对一系列问题的处理
- 事务是一个抽象层,应用程序->事务->数据库。
- 通过事务的抽象应用程序可以假装数据库不会崩溃(原子性),没有其他人同时访问数据库(隔离),存储设备是完全可靠的(持久性)
- 所有这些错误都被简化为事务中止,而应用需要的仅仅是重试
3. 事务的特性
3.1. A (原子性)
3.1.1. 原子性是什么
- 无论有多少条sql语句,要么整体执行失败,要么整体执行成功。
- 原子性
- 这个的原子性不是指并发的,即不是描述多个线程同时访问相同的数据会发生什么。并发是由隔离性描述的
- 用可终止性描述更为准确:一组操作如果中间发生了错误,那么可以终止这个事务,并且丢弃该事务进行的所有写入
3.1.2. 原子性是如何实现的
3.2. C(一致性)
3.2.1. 一致性是什么
- 系统从一个正确的状态,迁移到另一个正确的状态
- 当前的状态满足预定的约束就叫做正确的状态
- 一致性是最基本的属性,其它的三个属性都为了保证一致性而存在的。
3.2.2. 一致性是如何实现的
- 原子性,隔离性和持久性是数据库的属性,而一致性准确来说是应用程序的属性。应用程序可以通过数据库的原子性和隔离属性来实现一部分一致性,但更多的需要应用程序自己考虑。因此,一致性实现分为两个方面:
- 数据库本身:单个事务的一致性由原子性保证,并发事务的一致性需由隔离性保证。
- 应用层面:程序员自己保证
3.3. I(隔离性)
3.3.1. 隔离性是什么
- 多个事务同时访问相同的数据,可能会有并发问题。隔离性就是用来解决并发问题的
- 两个事务隔离意味着同时执行的事务之间不会相互干扰,也即事务A看不到事务B运行时的中间结果
3.3.2. 并发问题
3.3.2.1. 脏写
-
事务A修改了事务B修改过但未提交的数据
-
场景
- 如果有一个val是10,事务B先对其修改为10,事务A后对其修改为20,最后结果应该是20,但是实际上事务A的写被事务B的写覆盖了
事务A 事务B 写val为10(未提交) 写val为20(已提交) 写提交 读val为10
3.3.2.1.1. 如何解决脏写问题
- 行锁
3.3.2.2. 丢失更新
-
两个事务从数据库中读取一些值,修改它并写回数据库,即读取-修改-写入序列,这个操作不是一次性完成的
-
场景
- 如果有一个val是10,事务A和事务B都要对其+10,那么最终的结果应该是30,但是实际上事务B重写了事务A的val
事务A 事务B 读val为10 读val为10 写val+10为20(已提交) 写val+10为20(已提交)
3.3.2.2.1. 如何解决丢失更新问题
- 原子写
udpate table set val=val+10 where xxx=yyy
- 悲观锁
- 乐观锁
3.3.2.3. 脏读
-
事务A读取了事务B修改过没有提交的数据
-
场景
- 如果有一个val是10,事务B对其+10但没有提交,事务A两次读取应该是一样的,但是实际上事务A读到的结果不一样
事务A 事务B 读val为10 写val+10为20(未提交) 读val为20
3.3.2.3.1. 如何解决脏读问题
- 可以使用行锁,但是效率太差
- MVCC
3.3.2.4. 不可重复读
-
事务A两次查询,条件相同,但是结果不同。由于事务B提交后修改这条数据。
-
场景
- 如果有一个val是10,事务B对其+10并提交,事务A两次读取应该是一样的,但是 实际上事务A读到的结果不一样
事务A 事务B 读val为10 写val+10为20(更新+已提交) 读val为20
3.3.2.4.1. 如何解决不可重复读问题
- MVCC
3.3.2.5. 幻读
-
事务A两次查询,条件相同,数量不一致。由于事务B提交后新增了记录。
-
场景:
- 如果有一张表记录数为10,事务A插入一条新纪录,事务B两次读取记录数应该是10,但是实际上事务B读到的记录数不一样
事务A 事务B 读记录数为10 写入一条新纪录(新增或者删除+已提交) 读记录数为11
3.3.2.5.1. 如何解决幻读问题
- 串行访问
- 两阶段锁
- 谓词锁:不好实现
- 索引范围锁:MySQL用的就是这个,叫做Gap Locks
- 序列化快照隔离
3.3.3. 隔离级别
不同的隔离级别能解决不同的并发问题,每个具体的数据库隔离级别解决的问题不一样,但是一般来说,脏写问题太严重,每个数据库的任何隔离级别都不允许出现
- MySQL:InnoDB事务.md
- PostgreSQL:PostgreSQL隔离级别.md
3.3.4. D(持久性)
3.3.4.1. 持久性是什么
- 事务一旦提交,即使系统发生了崩溃,这个事务对数据库所作的更改也不能丢失
讨论
使用 GitHub 账号参与讨论,评论会保存在 GitHub Issues 中。在 GitHub 查看