第七章 事务
ACID的含义
原子性:
在多线程编程中,原子性指一个线程执行一个原子操作时,其他线程无法看到该操作的中间结果,只能观测到操作前或者操作后的状态;
而ACID中的原子性并不关乎多个操作的并发性,而是指客户端发起事务后,如果提交前发生error,如进程崩溃,网络中断等,则事务会进行回滚操作,保证事务要么成功要么失败;
一致性:
不同场景下的含义:
- 副本一致性;
- 负载均衡的一致性哈希;
- cap中的线性一致性,强一致性; ACID下,预期一致性;
即数据更改必须满足状态约束,事物从有效状态开始,最后的结果也必须符合有效状态;
但本质上此处的一致性更多是由应用层保证的;
隔离性:
并发执行的多个事务间相互隔离;
持久性:
保证事务提交成功后,即使硬件发生故障或者数据库宕机,也不会导致已持久化的数据丢失;
读提交rc
防脏读,如mysql,写请求时通过排它锁锁定数据行,事务提交后解锁,在读请求时 MVCC,每次读取都会生成 MVCC 快照,从快照中获取当前事务可读的最新值(通过事务id判断,不展开讨论了),一般就分为当前读(直接读取最新数据)、快照读(通过 undo log 的链读取可读的最新数据);
对于被修改的数据,数据库通常会同时维护新旧版本的值;
mysql中,旧值维护在 undo log 中
- undo log 的大小:是有限的,并且与 InnoDB 的配置有关。当 undo log 填满时,InnoDB 会清除那些已经不再需要的历史版本;
- 行的可见性: 只有
在事务开始之前已提交的版本
或事务开始后但是已提交的版本
会被暴露给事务读取。如果一个版本的行数据在当前事务开始之后被修改、未提交
或 被在当前事务开始后 才开始的事务修改
,那么旧版本的数据不会再对当前事务可见;- 清理(Purge): InnoDB 有一个后台线程,负责清理那些不再需要的历史版本和 undo log,这个过程会定期运行,确保数据库不会因为过多的历史版本而变得过于臃肿;
快照级别隔离与可重复读
防 修改相关的sql语句造成 的不可重复读问题;
使用快照级别隔离;(如mysql,即第一次读取数据库后就生成对应的快照视图,后续查询均通过该快照进行可读性判断:)
即每个事务都从数据库的一致性快照中读取,即使数据被修改,事务都只会观察到生成快照时间点的旧数据;
通常对不同的查询都会创建一个快照(mysql RR下,只会对第一查询创建快照),通过事物id区分哪些对象可见,哪些不可见,简单来说,事务开始前尚未提交的其他事物的修改均不可见;
防止更新丢失
原子写操作
通常的实现方法:
对读取/写入对象加独占锁(mysql而言,读取加共享锁,写入加排它锁);(可被称为游标稳定性)
强制所有原子操作单独在一个单线程上运行;(从根本上杜绝并发问题)
显式加锁
理清楚业务逻辑后,可选用for update等显式对数据行加锁,但这就依赖逻辑上的业务需求了;
自动检测更新丢失
即自动检测 读修改写回 的操作序列,是否有更新丢失的风险,有则直接终止该事务,mysql不支持;
类似于乐观锁检测;
原子cas
冲突解决与复制
对于多主,无主的多副本数据库,通常异步更新,则会出现多个并发版本,如果操作可交换,如计数器,则直接合并即可;
最后写入获胜(lww),容易丢数据,但也是大多数据库的默认配置;
写倾斜和幻读 KEY
写倾斜即 快照级别隔离下,读快照的同时,容易出现判断条件成立后,下一时刻修改数据时,条件不成立了,但是此时事务无感知导致错误执行;
此时书中给的场景需要select统计合法数据,再修改自己的数据,所以mysql在非串行化的隔离级别时,都会出现以上问题,两个事务交集部分没有写只有读;
类似其他读后条件不成立的场景,可被认为幻读,即事务的写入导致另一事物查询结果的变化,注意如果事务交集有写操作则问题不存在了,可能的解决方法:
- 可以显式加独享锁解决;
- 使用唯一索引;
- 串行化;
- 实体化冲突,较负责,即引入新字段,使事务间的交集包括该字段的写操作;
适合串行化的条件:
- 事务必须简洁高效,不能有长事务,否则会阻塞剩下排队的事务:
- 事务需要的数据可一次加载到内存中,如果大于内存发生内存交换,也很影响性能;
- 写入吞吐量不高,否则容易堆积;
- 跨分区事务占比小,否则容易出现偷懒/阻塞;
两阶段加锁2pl
即读取或写入数据时,对数据添加共享锁或者是排它锁,当事务提交后才释放锁,即两阶段;
原文提及:严格的ss2pl,用于mysql的可串行化隔离级别,即读加共享锁,写加排它锁; 此处应该指加表级锁;
且事务容易失败,性能低,需要有重试等兜底机制;
谓词锁:即临键锁,锁住查询区间的所有数据及其区间;
索引区间锁:由于谓词锁的检查匹配较为繁琐,故通过扩大锁定的区间,减少匹配的消耗,如锁定 该id的所有数据 而 不仅有该id被使用的部分;
可串行化的快照隔离SSI
使用乐观并发控制,事务中所有读取操作都是基于数据库的一致性快照,使用mvcc,发生写冲突时,只保留一个事务的提交;
需要简短的读写事务,长短均可的只读事务;