查询优化
数据库管理系统中非常重要的一部分。
代数优化
按照一定的规则将语句变化成关系代数以后进行优化
操作优化
对代数优化后的查询树使用比较好的方法进行查询。
主要是对连接运算进行优化
- 嵌套循环
- 归并扫描
- 索引优化
- 哈希连接
恢复机制
备份(完整备份+差异备份)+日志
事务
- A:原子性
- C:保持一致性
- I:隔离性
- D:持久性
事务的特性由DBMS负责维护,因此对于需要使用事务来进行执行的SQL语句,我们要定义在事务中。
如果没有显式地创建事务,那么DBMS会把每一条语句当作一个事务。
恢复信息
日志存储在非挥发存储器中。
- Commit list:已经提交的TID的列表
- Active list:进程中的TID列表
- Log:更新前和更新后的信息。存储的是修改前后的物理块的值
提交(commit)规则:在提交事务之前修改后的数据A.I必须写到非挥发存储器中
先记后写(log ahead)规则:在修改数据前必须把被修改数据的旧值B.I写到日志中
操作:
- 还原undo
- 重做redo
这两种操作具有幂等性:一次和和好多次是相等的
更新策略
A.I->DB before commit
TID->active list
…
B.I->Log
A.I->DB
…
TID->commit list
delete TID from active list
如果发生故障会启动重启动恢复:检查TID目前所处的状态
Commit list | Active list | Operation |
---|---|---|
No | Yes | undo,delete TID from active list |
Yes | Yes | delete TID from active list |
Yes | No | nothing to do |
为了避免每次检查都需要检查所有的TID,使用检查点。
检查点(check point):运行一段时间以后进行一次检查,并且设立一个检查点。每次检查检查上一次检查点以后的TID
A.I->DB after commit
TID->active list
…
A.I -> Log
…
TID->commit list
ALL:A.I -> DB
delete TID from active list
重启动恢复:
Commit list | Active list | Operation |
---|---|---|
No | Yes | delete TID from active list |
Yes | Yes | redo, delete TID from active list |
Yes | No | nothing to do |
这种策略的并发度更高。可以推迟加排他锁的时间。
A.I -> DB concurrently with commit
TID -> active list
…
A.I,B.I -> log
…
A.I->DB(partially done by 后台进程 when hard disk is free)
…
TID->commit list
A.I->DB(completed)
delete TID from active list
重启动恢复:
Commit list | Active list | Operation |
---|---|---|
No | Yes | undo, delete TID from active list |
Yes | Yes | redo, delete TID from active list |
Yes | No | nothing to do |
总结
redo | undo | |
---|---|---|
A.I->DB before commit | No | Yes |
A.I->DB after commit | Yes | No |
A.I->DB concurrently commit | Yes | Yes |
异地更新(有缺点,没有被推广) | No | No |
并发控制
并发:支持多个事务同时访问数据库
原因:
- 改善系统的利用率
- 不同的事务很可能访问的是不同的数据,互不冲突
并发控制:对事务的并发运行加以管理
任意并发的后果:
- 丢失更新:写-写冲突
- 读脏数据->恢复时的多米诺现象:写-读冲突
- 不可重复的读:读-写冲突
可串行化:并发运行事务以后的结果如果和某种串行运行的结果相同,则说这种并发运行是可串行化的,即是正确的。
如果用户把一些事务同时提交并发运行,则要求这些事务谁先运行后运行是无所谓的,即默认所有可串行化的结果都是正确的。
并发控制策略
通过并发控制使得并发事务的运行是可串行化的
封锁法
通过锁对事务强行串行化
- X锁协议(排他锁)
定义1:在一个事务里面,如果所有的加锁请求都在锁释放之前,称这个事务是一个两阶段事务,符合两阶段加锁协议。(增长阶段-缩减阶段)
定义2:先得到锁再访问数据对象,那么这个事务就是well-formed(合式的)
定义:如果每个事务是合式的两阶段事务,那么这些事务一定是可串行化的。
如果事务是合式的并且是两阶段事务,并且在事务结束的时候释放更新锁,那么这个事务是可串行化的、可恢复的。不会出现恢复的时候的多米诺效应
如果在事务结束的时候释放所有的锁,那么称这个事务满足严格的两阶段加锁协议。
NL | X | |
---|---|---|
NL | Y | Y |
X | Y | N |
数据库效率比较低。
- SX锁协议
S(hare) lock :如果是读操作
X lock:如果是更新操作
NL | S | X | |
---|---|---|---|
NL | Y | Y | Y |
S | Y | Y | N |
X | Y | N | N |
- S(share)U(update)X locks
NL | S | U | X | |
---|---|---|---|---|
NL | Y | Y | Y | Y |
S | Y | Y | Y | N |
U | Y | Y | N | N |
X | Y | N | N | N |
系统的并发度较高。
死锁/活锁(饥饿)
活锁/饥饿:优化调度策略
死锁:
- 防:防止出现死锁
- 治:出现死锁以后能够解决死锁。
治:
- 当事务获得锁以后的等待时候超过一个限度以后就判定已经发生了死锁,就重启事务。对于时间的设置影响系统的运行效率
- 构造等待图
节点:等待的事务
边:等待关系
如果在等待图里面出现环就说明出现死锁。
检查时机:每次出现新的等待关系的时候/周期检查
解决方法:选择一个牺牲者(目前拥有锁最小的/滚回代价最小的事务)。然后等待环路上的其他事务都运行结束以后再运行该事务。
防
操作系统中的解决方案:
- 检查所需要的所有资源
- 给资源进行排序
在数据库系统中不现实
多粒度加锁
- 一旦遇到得不到锁就终止,不等待就不会死锁
- 事务重试:给每一个事务安排一个时间戳
- 当作TID
- 比较两个事务的年龄
等待死亡协议:
如果Ta需要申请一个锁,这个锁已经被Tb占领了:
- 如果Ta比Tb年老,则Ta进行等待
- 如果Ta比Tb年轻,则自己终止,然后自动重新运行(以原来的时间戳)
因此不可能重现循环等待,解决了死锁和活锁问题
受伤等待协议:
如果Ta需要申请一个锁,这个锁已经被Tb占领了:
- 如果Ta比Tb年轻,则Ta进行等待
- 如果Ta比Tb年老的话,则将Tb终止