lecture 10:Sorting & Aggregation Algorithms
table中的tuples是无序的,但是许多操作都需要排序,例如ORDER BY, GROUP BY, JOIN, DISTINCT。对于clustered B+tree,可以从左到右遍历叶子节点得到有序的tuple。对于unclustered B+tree一般通过外部归并排序来做。
对于GROUP BY,DISTINCT这些不需要排序的Aggregation场景,使用hashing的方法相比sort是一个更好的选择。hashing方法也能在内存不足的情况下借助磁盘完成任务。
lecture 11:join algorithms
在使用数据库时,我们常常遵从常用的设计范式,而这些范式的核心目的就是减少重复。而在读取数据库时又常常需要把这些数据“组装”起来使用,这就是join的作用。
对于join的理想实现,是让两个table 中的tuple做笛卡尔乘积,生成一个新的table,里面存放join的结果。但是基本不可能这么做,因为一般table中的tuple有很多,另外可能一个tuple有很多column,生成的结果会非常大。
有很多算法来减少join操作的开销,但没有哪一种是适合所有场景的。
- nested loop join
- stupid:多层for循环,单位是tuple
- block:也是多层for循环,但是单位是page或者block
- index:借助index查找join key相等的tuple
- sort-merge join。分为两阶段
- sort阶段将两个table按照join key进行排序,可以使用上一节讲的外排序
- merge阶段分别两个有序的table,将两个join key一致的tuple组合起来作为join的结果
- hash join。也分为两阶段
- build阶段,使用一个hash函数h根据join key将outer relation 建立一个hash table。(也可以在这一阶段创建一个bloom filter,加速probe阶段)
- probe阶段,对inner relation中的每一个tuple,都使用hash函数h在上面的hash table中搜索,寻找outer relation中join key值相同的tuple。
- grace hash join
lecture 12: Query Processing I
query processing 是从数据库中进行数据操作所进行的一系列动作。一般包括三个步骤:语法解析与翻译;优化;执行。也就是从用户输入SQL语句到得到结果的这一个过程。
- 语法解析与翻译。SQL语言适合人使用,但是并不适合机器,因此一般将查询语句翻译为以关系代数为基础的表达式。这一过程类似于编译器的语法分析器。
- 优化。同一个查询语句可以翻译为不同的关系代数表达式,而且在关系代数表达式上可以建立的不同查询执行计划,不同的查询计划会有不同的代价。构造具有最小查询代价的查询计划叫做查询优化。
- 加了“如何执行”注释的关系代数运算称为“计算原语”。(例如走index scan并操作,走seq scan的或操作)
- 用于执行一个查询的原语操作序列成为查询执行计划。
- 查询执行引擎接受一个查询计划,执行该计划并将结果返回给查询。
- 执行。以单个关系运算的执行为基础,多个运算组成的表达式一般有两种执行方式:
- Materialization Model。从树的最底层开始,每个运算的输入是更低一层的输出,输出是一个临时relation。也就是说所有中间结果都被作为临时relation被创建,甚至被写到磁盘。
- pipeline Model。对于一个tuple来说,在表达式中被流水线化地处理。中间结果不必长时间保存,并且能够最大限度重用各个操作的代码。在实现上有两种方式:
- demand-drivern pipeline,需求驱动的流水线,树的顶层不断向底层发送获取tuples的请求。每个操作都可以用迭代器iterator的方式实现,实现简单。
- producer-drivern pipeline,生产者驱动的流水线,树的低层积极地生成tuples
对于多种可能的查询计算计划,希望根据代价对它们作比较,并选择最佳方案。一般,代价是由查询计划对各种资源(CPU时间,磁盘存取时间,并行/分布式数据库通信开销)的使用情况来度量的。磁盘通常是最主要的代价。
lecture 13:Query Processing II
主要讨论了基于关系数据模型的并行数据库。
- interquery parallelism,多个查询并行执行。主要是为了提高事务处理能力
- intraquery parallelism,单个查询在多个处理器和磁盘上并行执行。有两种并行方式
- intraoperation parallelism,操作内并行,比如并行地排序,并行作join。见18.5章
- interoperation parallelism,操作间并行。见18.6章
即使我们能把一个操作非常高效地并行处理,但是磁盘还是很可能会成为瓶颈。
- IO parallelism。将一个relation划分到多张磁盘上,来分散IO的压力。常用的形式就是水平分区,把一个relation中的所有tuple分散到多个磁盘上。有好几种划分策略
- round-robin。对于点查和范围查询效率都不高
- hash partitioning。对于扫表和点查很有效。但是范围查询效率不高
- range partitioning。点查和范围查询都还可以。但是出现skew的可能性比较大。
lecture 14 & 15:Query Planning & Optimization
这一节和下一节主要讲查询优化的东西。query optimization是一个NP-HARD的问题,也是DBMS中最难的问题。
SQL是命令式的,正如lecture12中所说,对于一个给定的用户查询,DBMS可以用不同的关系代数表达式去表示它,每一个关系代数表达式也能得到多种多样的查询执行计划。query optimization的目的就是对于一个到来的查询,能够选出一个“最有效”的查询执行计划。
首先要知道所有的可用的查询执行计划。给定一个用户查询,生成查询计划有两个步骤:
- 产生逻辑上与给定关系代数表达式等价的表达式。通过等价规则来进行(13.2章),也叫做query rewriting
- 对所产生的表达式以不同方式做注释,产生不同的查询计划
生成路径如下: SQL语句 –> 关系代数表达式 –>(加注释)–> 计算原语 –>(组装)–> 查询计划。
有两种常用的方式来进行查询优化,也就是选出一个最有效的查询执行计划:
- Heuristics/Rules。启发式/基于规则。根据经验设置一些规则,对于不符合此规则的查询执行计划不视作可能的最有效查询执行计划。通常用作缩小搜索空间(减少需要考虑的查询执行计划的数量),降低查询优化本身的代价。一些常用的规则有:
- Predicate Push-down。尽早执行filter运算
- Projections Push down。尽早执行projection运算
- Expression Simplification。
- Cost-based Search。基于代价的搜索方法。
- 代价的计算并不依靠查询执行计划的真实执行。DBMS会在内部的catalog中存储每个relation的统计信息——tuple个数、指定属性的value distribution(直方图)、索引等等。直方图并不需要保存每个值的信息,可以把许多值用一个bucket来表示,来减小直方图的大小。
针对单个relation的OLTP查询优化比较简单,通常只需要挑选最优的索引。
对于嵌套查询,比如where里面的查询子句,通常是将子句作为一个单独的函数,将他的输出写入到一个临时的表。
lecture 16:Concurrency Control Theory
这节主要讲事务transaction。对事务的要求一般采用ACID来描述:
- Atomicity。All or nothing
- approach 1:Logging
- approach 2:Shadow Paging
- Consistency。要求事务执行后能够保持数据库的一致性,例如主码约束、参照完整性、check约束等。由事务编写者负责。
- Isolation。每个事务都感觉不到系统中有其他事务在并发执行。
- Durability。事务commit之后,所有的更改都是持久化的,即使DBMS崩溃了
Atomicity和Durability主要由recovery system负责。这里主要讲实现Isolation的并发控制系统的内容。
为了高效,使用并行的方式执行transactions,但是并发会带来很多并发问题。对于确保并发执行事务不“出错”。需要做两件事:
- we need formal correctness criteria to determine whether an interleaving is valid
- 定义:database被视作一个data object的固定集合(A,B,C……)。(不讨论insert和delete,这两者会导致新的问题——phantom problem)
- 定义:transaction被视作一个读写操作的序列(R(A), W(B), R(B)……)
- correctness criteria就是 ACID。
- 识别哪些并发情况是正确的,哪些是不正确的
- 如果一个schedule等同于某个serial execution,那么这个schedule就是正确的。这种schedule也叫做serializable schedule。
- 什么schedule?多个transaction的operation一种交错排列。serial schedule就是多个transaction顺序排列。
- 什么是等同?对于任何一个database state,两个schedule执行后的effect等同。
- conflict。如果两个来自不同transaction的operations,操作同一个data object,并且至少一个是write。那么这两个operation就有了conflict。
- Read-Write conflicts。也叫做unrepeatable reads
- Write-Read conflicts。也叫做reading uncommitted data,“dirty reads”
- Wirte-Write conflicts。也叫做overwriting uncommitted data
- 如何做到正确。可以通过检查一个schedule是否存在conflict 来判断是否是serializable schedule
- conflict equivalent
- conflict serializable。通过precedence graph实现
- 那么如何产生一个serializable schedule,也就是如何协调conflict来做到并行且安全的schedule:
- pessimistic。例如two-phase locking
- optimistic。例如 timestamp ordering
lecture 17:Two-Phase Locking
这一节承接lecture 16的内容,主要讲2PL,这是一个能生成serializable schedule 的一个算法。
如何证明根据two-phase locking protocol生成的schedule是一个serializable schedule?
2PL的问题:
- 级联回滚,cascading aborts。普通的2PL不能阻止dirty reads,可能导致读到了脏数据的事务级联回滚。strict two-phase locking protocol(要求事务commit之前不得释放所有的X锁)和rigorous two-phase locking protocol(要求事务commit之前不得释放所有锁)可以解决。
- 死锁,deadlock。主要有两种解决方法(感觉主要就是悲观和乐观的区别)
- deadlock prevention,死锁预防
- deadlock detection & recovery,死锁检测和恢复
锁是由lock manager统一管理,并且有不同粒度的锁。将database抽象成一棵树,对一个节点加锁,通常也意味着要对子树的访问有所限制。意向锁
lecture 18:Timestamp Ordering
这一节也算是承接lecture 16的内容,主要讲乐观的生成serializable schedule 的一个算法——基于时间戳的协议。“时间戳排序协议保证冲突可串行化,这是因为冲突操作按时间戳顺序进行处理。也保证无死锁,因为不存在事务等待。但是可能导致长事务饿死。”
主要讲了两个算法:Basic Timestamp Ordering Protocol、Optimistic Concurrency Control
lecture 16中说到 “不讨论insert和delete,这两者会导致新的问题——phantom problem”。2PL不能解决这种问题,意向锁可以解决phantom的问题。另一个解决phantom比较常用的方式是通过predicate locking来解决——也就是对指定表达式加锁(比如where age=18、where age<20)。但是predicate locking很难实现,而且开销很大,如果指定字段上有index,一般使用index locking的方法。
下面的内容来自2018年课程视频,因为2019年没讲。isolation levels,是说一个transaction向其他正在并行运行的txn所暴露的程度等级。越多地将一个txn未提交的内容暴露出去,并发度就越高。根据dirty reads、unrepeatable reads、phantom reads将隔离等级从高到低分为四种:
- serializable。上面三种情况都不允许
- repeatable reads。可能会有phantom reads
- read committed。可能会有unrepeatable reads和phantom reads
- read uncommitted。三种情况都允许
lecture 19:Multi-Version Concurrency Control
MVCC的design decision主要有:
- concurrency control protocol
- 可以使用2PL,也可以使用timestamp ordering
- version storage
- garbage collection
- index management
lecture 20 : Logging Schemes
这一节主要讲DBMS是如何应对故障的。也就是recovery schema(恢复机制)是如何将数据库从故障中恢复到一致的状态,以及将数据库的不可用时间缩减到最短(高可用 high availability)。保证事务的ACD。
故障的分类:
- transaction failure。
- logical error。比如非法输入
- system error。比如死锁
- system crash。硬件故障,或者DBMS、操作系统故障,但是disk不受影响(fail-stop assumption)。
- disk failure。没有DBMS可以处理这种情况,只能依靠周期性转储来恢复database。
目标:
- 如果DBMS说某个事务已经提交了,那么这个事务的所有更改都应该已经持久化
- 如果某个事务aborted,那么不允许这个事务所做的任何更改
故障恢复算法分为两部分:
- 在正常处理事务时采取措施,保证有足够的信息可以用于故障恢复。(也就是视频课程这一节主要讲的内容)
- 故障发生后采取措施,将数据库恢复到某个一致性的状态。(lecture 21)
DBMS如何支持故障恢复,取决于它是如何管理buffer pool的。
如上图,在T1 abort 之前,T2 commit。DBMS在两个问题上会有取舍:
- 一个事务真正commit之前,是否强制将其更改写入到磁盘 (force or not)
- 在将buffer pool的内容刷新到磁盘上时,是否允许未提交事务的更改写入到磁盘 (steal or not)
现在广泛使用的WAL策略,其中一个原因就是它支持steal+no-force的buffer pool管理策略,比较高效。 WAL(write-ahead logging)规则:内存中的数据块输出到磁盘前,所有与该数据块中数据有关的日志记录必须已经输出到稳定存储器。
一个事务的相关日志记录写入到稳定存储器之后,这个事务才被认为是committed。
要支持steal+no-force的buffer pool管理策略,就要解决两个问题:
- 事务已经提交,但是它对数据库的修改仅仅存在于内存中,还没同步修改磁盘,然后crash。(redo)
- 事务还未提交,但是已经修改了数据库,且同步到了磁盘上,然后crash,这个事务需要回滚。(undo)
一条log record至少要包含:
- transaction id
- object id
- old value(用来做undo)
- new value(用来做redo)
另外,日志以何种形式记录呢?
- Physical Logging。以raw bytes的形式记录对数据库的修改
- Logical Logging
- Physiological Logging
系统发生故障后,必须检查日志,决定哪些事务需要redo,哪些事务需要undo。搜索整个日志来确定该信息非常耗时,因此引入了检查点checkpoint。
一个简单的检查点创建过程如下:
- 此过程中,不允许事务执行更新操作,如写缓冲块或写日志记录
- 将当前位于主存的所有日志记录输出到稳定存储器
- 将所有修改的缓冲块输出到磁盘
- 将一个日志记录
<checkpoint L>
输出到稳定存储器,其中L是此创建点创建过程中尚在活跃的事务列表 checkpoint是怎么创建的,多久创建一次,也是一个需要trade-off的问题。
对于disk failure的情况,一般采用转储(dump)的方式:比如一天一次进行备份。转储的方式一般也要求转储过程中不能有事务处于活跃状态,类似于检查点。不过都有改进措施。
lecture 21:ARIES Database Crash Recovery Algorithms
这一节主要讲ARIES,Algorithms for Recovery and Isolation Exploiting Semantics,一种故障恢复算法。
每个log record额外包含一个全局唯一的编号,就是log sequence number LSN。DBMS的各个组件通过LSN来追踪日志。
三个阶段:
- 分析阶段,analysis pass
- redo阶段,redo pass
- undo阶段,undo pass
没仔细听,可以重新看一下课程视频,看一下别人的笔记https://www.jianshu.com/p/ea61881309df
lecture 22:Introduction to Distributed Databases
Distributed DBMSs的架构主要有:
- shared-memory
- shared-disk。这种也是很常见的,因为cpu-memory这些计算资源是无状态的,可以很好地创建销毁迁移。(类似于存算分离)
- shared-nothing。节点之间只通过network来交流。
design issues:
- application如何查询数据
- 如何执行query
- 如何保持一致性
hash partitioning,consistent hashing
分布式环境下的事务需要一个协调者coordinator,协调者的选取有两种方式:
- centralized,有一个全局的协调者
- decentralized,节点间自己决定
lecture 23:distributed OLTP database
- OLTP特点:
- 一个事务的运行时间较短
- footprint比较小
- 重复的操作
拜占庭算法(也就是区块链的共识算法)
atomic commit protocol,一个涉及到多节点的事务如何安全地提交,做法有:
- two-phase commit
- three-phase commit(很少用)
- paxos
- raft
- zab(apache zookeeper)
- viewstamped replication
two-phase commit需要所有的节点都同意commit,才算真正的commit。 paxos只需要大多数的节点同意commit,就算commit。
replication:
- configuration:
- approach 1:master-replica。不需要使用atomic commit protocol
- approach 2:multi-master。需要使用atomic commit protocol
- k-safety。确保每份数据至少被replicate k份
- propagation scheme。通知客户端commit完成之前是否把事务的更改更新到replicas
- synchronous,也叫strong consistency
- asynchronous,也叫eventual consistency
- propagation timing。
- approach 1:continuous
- approach 2:on commit
CAP理论:
lecture 24:distributed OLAP databases
query plan fragment
- approach 1:physical operators,把一个query plan分成partition-specific fragment
- approach 2:rewrite SQL,把初始的query改写为partition-specific queries
cloud system:
- approach 1:managed DBMSs。云厂商托管数据库,数据库本身是不知道自己运行在云环境中
- approach 2:cloud-native DBMS。为云环境所设计,通常基于shared-disk
serverless database
lecture 25:Oracle in-memory databases
可以看出近几年Oracle的主要工作方向就是in-memory。
思维导图
参考链接
- 《CMU15-445课程》https://15445.courses.cs.cmu.edu/fall2019/schedule.html
- 《别人的学习笔记》https://www.jianshu.com/nb/36265841
- 《课程视频》https://www.bilibili.com/video/BV1q741127SQ
- 2018年的课程视频:https://www.bilibili.com/video/BV1ft411B7nz