事务部分
(未完待续)
事务
- DB中执行的最小单元
- 具有ACID特性
- A——原子性
- C——一致性
- I——隔离性
- D——持久性
- 控制命令
- BEGIN
- COMMIT
- ABORT
- 影子分页(属于No-Steal + Force)
- 数据库维护两个独立的数据版本(有点类似于主从复制模型)
- master——负责其他事务的读取
- shadow——负责写事务的修改操作
- 提交完成后,将shadow版本切换至master版本
- 用例——LMDB
- 日志
- Redo日志(仅记录修改后的新值,所以只能重做,不能回滚)(属于No-Steal + Force)
- 每次修改,产生redo日志
- 事务commit前,事务所有修改不能落盘
- 事务commit成功前,事务的所有log必须先落盘
- Undo/Redo日志(同时记录旧值和新值,所以既可以实现回滚,又可以实现重做)(属于Steal + No-Force,对大事务友好,也减轻了写盘压力)
- Redo日志(仅记录修改后的新值,所以只能重做,不能回滚)(属于No-Steal + Force)
- 日志回收
- 数据库维护两个独立的数据版本(有点类似于主从复制模型)
隔离性
如何保证隔离性的同时实现并行?
想像A、B余额都为1000,事务T1:A转账100给B,事务T2:支付A、B 6%的利息
T1: T2:
BEGIN BEGIN
A -= 100 A *= 1.06
B += 100 B *= 1.06
COMMIT COMMIT
矛盾
- 两个事务,在同一块数据上,而且至少有一个涉及写操作
隔离级别(从低到高)
Read Uncommited(导致脏读)
/Users\bruce\AppData\Roaming\Typora\typora-user-images\image-20210818173642476.png)
Read Committed(可能会导致不可重复读)
MVCC可以解决(MVCC中,T1第二个R(A)仍然读为$10)Repeatable Read(可能会导致幻读)(MySQL默认级别,InnoDB通过MVCC解决幻读问题)
Serializable
问题
- 不可重复读
- 脏读
- 写写冲突
可串行化
- 矛盾可串行化
- 视图可串行化
如何可串行化
- 交换
- 依赖图
悲观
让问题第一时间不发生(严格)
- 2PL
- 锁种类
- Lock
- 隔离事务
- 保护数据库的内容
- 处于事务内
- 有共享锁、排他锁、更新锁
- Latch
- 隔离线程
- 保护内存中的数据结构
- 位于临界区
- Lock
- 2PL
- 共享锁和排他锁
- 不需要提前知道事务中的内容
- 分为两阶段
- growing——可以通过管理器获取锁
- shrinking——只能释放,不能获取
- 缺陷
- 可能引起级联中止
- 死锁
- 仍然会脏读——需要严格2PL
/image-20210812235111600.png)
- 事务提交前不解锁
- No-steal,不会引发级联中止
- 死锁检测和预防
- 选择受害人
- 锁种类
乐观
2PL假设事务内部很多冲突
但是如果问题很少发生,可以当其发生时才解决
时间戳排序
- Basic T/O
- Ts(Ti) ——每个事物开始执行的时标
- 系统时钟作为时间戳
- 逻辑计数器
- 写时间戳W-Ts(X)——成功实行W(X)的所有事物的最大时间戳
- 读时间戳R-Ts(X)——成功实行R(X)的所有事物的最大时间戳
- 例子
- Ti需要R(X)
- if Ts(Ti) < W-Ts(X), 证明此时读到的值已被覆盖;反之则可以读,R-Ts(X)设为max(R-Ts(X), Ts(Ti))
- Ti需要W(X)
- if Ts(Ti) < R-Ts(X), 证明此时读到的值是先前需要的值;反之则可以写,W-Ts(X)设为max(W-Ts(X), Ts(Ti))
- if Ts(Ti) < W-Ts(X), 证明此时读到的值已过时;反之则可以写,W-Ts(X)设为max(W-Ts(X), Ts(Ti))
此时T1的W(A)执行是满足2PL的,但不满足时间戳排序(Ts(T1) = 1 < W-Ts(A) = 2),故引出托马斯写规则
- Ti需要R(X)
- Thomas写规则
- if Ts(Ti) < R-Ts(X),中止事物并重启Ti
- if Ts(Ti) < W-Ts(X),托马斯规则认为:忽略写,允许事物执行
- 其他情况,允许Ti写,并更新W—Ts(X)
- 缺陷
违反了可恢复?- 开销大,需要复制到本地工作区
- 长事物中途可能需要暂停
- Ts(Ti) ——每个事物开始执行的时标
- Optimistic
- 创建一个private workspace for each tx
- 当tx提交,DBMS检查是否有冲突,如果没有就写到全局区域内
- 步骤
- 读阶段——读入到局部变量中
- 验证阶段——判断是否可以写且不违反可串行性
- Start(Ti)/Validation(Ti)/Finish(Ti),将validation(Ti)作为事务Ti的时间戳
- 有效性检测的原则(Ts(Tk) < Ts(Ti)),需满足两者之一
- Finish(Tk) < Start(Ti)——这保证了可串行性(较强)
- Tk所写的数据项和Ti所读不相交,且Tk写完成的时间在Ti验证之前(Start(Ti) < Finish(Tk) < Validation(Ti))。这个保证可串行性
- 因为前提Tk的写不影响Ti的读,所有两个阶段可以重叠
- Tk写完,Ti还没写,没有重叠
- 写阶段——将临时变量写入数据库中
- 特点
- 不会发生级联中止
- 长事务会饿死
- Partition-based Timestamp
- Basic T/O
MVCC
以上的机制都相对悲观
具体设计
并发控制
- 写不阻塞读,读不阻塞写
- MVCC为每个W(X)创建一个新版本(类似于影子页),只写事务可以在不需要锁的前提下读数据库一致的快照(虚拟),很好的支持time-travel查询?
- 例子
数据存储
- 怎么快速找到这个版本?构造一个链表
- append-only
- 每次都在表后增加一个版本,同一个数据项的新老版本连接起来(用单链表)
- 表中存放整个数据行
- 连接顺序
- O2N从老到新,这样获取最新版本需要从头到尾遍历
- N2O从新到老,这样每次遍历的头结点都是变化的
- 优化——构造一个索引,指向各个数据项的最新版本
- time-travel
- 跟N2O的优化类似,分成master版本和time travel版本,master版本储存最新的数据
- 表中存放整个数据行
- delta storage
- 存放的不是整个数据行
- 存放的是增量值(类似于用于回滚的undo日志)
- append-only
- 怎么快速找到这个版本?构造一个链表
垃圾清理
- 元组水平
- 事务水平
索引管理
/image-20210815225414589.png)