这是对沈询大神讲解的事务 的笔记。
事务简介
事务的本质是锁和并发
ACID保证事务的完整性
事务也称为单个事务单元。
例如
转账:A-->B 100元A账户减少100,B账户增加100。
在此操作过程中必须要保证完整,一致。这就是一个事务单元。
除了转账的问题还有一些也是事务单元:
* 建立索引
* 从数据库中读取一条数据
* 向数据库中写入一条数据,同时更新这行记录的所有索引
* 删除 整张表
一组事务单元 :
多个事务单元之间的happen-before关系:
* 读写
* 写读
* 读读
* 写写
如何对一组事务单元的关系进行排序是很重要的
- 序列化读写1
- 排它锁[^2]
- 读写锁[^3]
- MVCC
MVCC: copy on write针对写读场景的优化。 在写的时候,依旧可以进行并发读。
它可以做到 读读不冲突,读写不冲突,写读不冲突。
本质是每一次数据写入都放入一个log里面
隔离性是对一致性的一种破坏
处理事务的常见思路
事务处理的常见问题
- 多个事务单元,谁先谁后?
- 如何进行故障恢复?
碰到死锁如何处理?
- 在orcle中是SCN(逻辑时间戳) 在inno_db中是Trx_id
- 业务属性不匹配,记录每一步操作的反向操作。 如果数据库崩溃,那么重启后在进行数据恢复的时候,在整个回滚过程中,这个系统是无法被外界访问的。
- 死锁与死锁检测: 死锁产生原因: 两个线程,不同方向加锁,相同的资源
死锁检测
碰撞检测 等锁超时 降低隔离级别
深入单机事务
事务的ACID
- 原子性
- 一致性
- 隔离性
- 序列化读
- 可重复读 (读加锁,只能做到读)
- 读已提交 (读锁可以被写锁升级。 可以读写 但是不可以 写读)
- 读未提交 (可能会读到中间状态的值,比如转账过程中,两个人钱都为0)
- 持久性
原子性
一个事务要么同时成功,要么同时失败。原子性只保证了记录了一个回滚段,
在事务失败的时候,可以回滚到上一个操作的状态。
一致性
多个事务之间的 happen-before关系
将所有的请求,排队。
最终要的部分就是如何处理不同事务之间的读写并行
隔离性
以性能为理由对一致性的破坏
除了sql92的 四个隔离级别之外 又有了一个新的隔离级别, 快照隔离级别。
mvcc 无锁编程 快照读可以保证在读到一致性的同时实现读未提交。
当读大于写的时候会增加系统的成本。 适合读写比率比较高的情况。
持久性
事务完成之后,该事务对数据库所做的所有更改便持久的保存在数据库中。
将请求打包后提交
* 直接写入内存 优点:IOPS很高 缺点: 很可能丢失数据
* GroupCommit 有点:保证系统的持久性和吞吐量 缺点: 请求的延迟高
事务的调优原则
- 在不影响业务的情况下,尽可能减少锁的覆盖范围
- 在不影响业务的情况下,增加锁上可以并行的线程数
- 读锁,写锁分离。
- 允许并行读取数据。
- 在不影响业务的情况下,选择正确的锁类型
悲观锁 使线程到blocking状态 适合并发争抢比较严重的场景
乐观锁 适合并发争抢不太严重的场景
事务个各个不同隔离级别可能会带来的问题
脏读
在一个事务正在访问数据,并对数据进行了修改,这种数据还没提交到数据库,
被另一个事务所读到这个数据。
这个应该是 读未提交引发的,即写读可并行。
幻读
目前工资为1000的员工有10人。
事务1,读取所有工资为1000的员工。
这时事务2向employee表插入了一条员工记录,工资也为1000
事务1再次读取所有工资为1000的员工 共读取到了11条记录,
解决办法:如果在操作事务完成数据处理之前,任何其他事务都不可以添加新数据 ,则可避免该问题
这个从应该是 读写 可并行引起的。
不可重复读
不可重复读的重点是修改 :
同样的条件, 你读取过的数据,再次读取出来发现值不一样了
幻读的重点在于新增或者删除 :
同样的条件, 第 1 次和第 2 次读出来的记录数不一样
- 该方式的优势是 不需要冲突控制劣势是慢速设备
[^2]: 两个事务单元之间完全没有冲突则进行并行
[^3]: 写读之间完全分开,让读读并行 ↩