上一篇(ch.6)

下一篇(ch.8)

第七章 事务

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,发生写冲突时,只保留一个事务的提交;

  需要简短的读写事务,长短均可的只读事务;