事务是关系数据库操作的逻辑单位。事务的存在,是为从数据库层面保证数据的安全性,减轻应用程序的负担。
数据库事务性
事务应该具有4个属性:原子性、一致性、隔离性、持久性。
- 原子性(Atomicity):事务作为一个整体被执行,其中的操作要么全部被执行,要么都不执行。
- 一致性(Consistency):
- 隔离性(Isolation):
- 持久性(Durability):
隔离级别
- 读未提交(Read Uncommitted):会出现脏读(Dirty Read),事务会读到另一个事务的中间状态。
- 读已提交(Read Committed):会出现不可重复读(Unrepeatable Read),会读到已提交的数据,但是一行数据读取两遍得到不同的结果。
- 可重复读(Repeatable Read):会出现幻读(Phantom Read),事务执行两个相同的查询语句,得到的是两个不同的结果集(数量不同)。
- 可串行化(Serializable):可以找到一个事务串行执行的序列,其结果与事务并发执行的结果是一样的。
Snapshot Isolation
表膨胀是指表的数据和索引所占文件系统的空间,在有效数据量并未发生大的变化的情况下,不断增大。
表膨胀的直接触发因素是表上的大量更新,如全表的update操作、大量的insert+delete操作等。而我们知道,PG在更新数据时,是不直接删除老数据的。一个update操作执行后,被更改的数据的旧版本也被保留下来,直到对表做vacuum操作的时候,才考虑回收旧版本。做数据更新时,这些旧版本不及时回收就会造成表膨胀。
MVCC
如果数据库中的所有事务都是串行执行的,那么它非常容易成为整个应用的性能瓶颈,虽然说没法水平扩展的节点在最后都会成为瓶颈,但是串行执行事务的数据库会加速这一过程;而并发(Concurrency)使一切事情的发生都有了可能,它能够解决一定的性能问题,但是它会带来更多诡异的错误。
MVCC(Multiversion Concurrency Control) 多版本并发控制
是数据库引擎实现中常用的处理读写冲突的手段,目的在于提高数据库高并发场景下的吞吐性能。MVCC
意图解决读写锁造成的多个、长时间的读操作饿死写操作问题。每个事务读到的数据项都是一个历史快照(snapshot)并依赖于实现的隔离级别。写操作不覆盖已有数据项,而是创建一个新的版本,直至所在操作提交时才变为可见。快照隔离使得事物看到它启动时的数据状态。
MVCC
使用时间戳
或自动增量的事务ID
实现事务一致性
。MVCC可以确保每个事务(T)通常不必“读等待”数据库对象(P)。这通过对象有多个版本,每个版本有创建时间戳 与废止时间戳 (WTS)做到的。
事务Ti读取对象P时,只有比事务Ti的时间戳早,但是时间上最接近事务Ti的对象版本可见,且该版本应该没有被废止。
事务Ti写入对象P时,如果还有事务Tk要写入同一对象,则(Ti)必须早于(Tk),即 (Ti) < (Tk),才能成功。
MVCC可以无锁实现。
MVCC Meta Data
Volcano
Volcano-An Extensible and Parallel Query Evaluation System
Volcano重点关注的是扩展性和并行性。Volcano是第一个融合扩展性和并行性的查询引擎。
扩展性:查询引擎可以比较容易的应用在不同数据库系统里,通过简单修改就可以适配新数据类型,新算法,新算子。
并行性:不同的算子可以很方便的并行运行,不同的数据分片可以很方便的被并行处理。
Volcano将系统设计分为两个部分,下面的模块是文件系统,上面模块是查询处理系统。
Volcano文件系统包括缓存管理,文件记录,B树索引等。底层模块提供机制,上层调用指定策略决定如何组合底层的机制来解决问题,这也是扩展性的一个方面。
Volcano查询处理系统中每个算子都应该实现成一个iterator,有三个接口,分别是open/next/close,其中open可以做一些资源初始化,比如打开文件,next将游标向前推进返回Next-Record,close负责销毁资源。每个算子并不关心其他算子是干什么或者怎么干的,只要实现这三个接口大家就可以配合工作。
易于添加新的iterator:整个算子体系就是一堆实现了open/next/close迭代器的组合,那么想实现了一个新的算子放入这个体系也变得非常容易,并且对系统其它部分无感知。
易于异构化,并行化:本质上上游的iterator只关心下游iterator送来的数据,至于该下游iterator是如何得到这些数据的则完全不关心,所以这就使得我们很容易将iterator放到不同的CPU(分布式系统里面就是不同计算节点)运行,简化了并行化的复杂度,并可以将并行化做成标准流程,为所有算子共享。
易于重新组合算子:查询优化中可以基于一些重写规则或者统计信息来重新定义各个算子执行的顺序,从而达到优化查询的目的,而Volcano体系结构中,允许各个算子自由组合,那么这就使得查询优化变得更加容易实现。
input
就是来自下游iterator的输出,理论上是一片字节数据,解释这些数据靠的是支持函数。
支持函数(support function)
作用上可以理解为虚拟文件系统的file_operations结构,里面以函数指针的形式规定了众多文件操作声明。
chose plan operator
执行过程中允许动态的选择一些执行路径。动态执行不是通过iterator加一层实现的,而是在查询优化阶段就确定了,而查询优化阶段跟执行阶段是并列的,并不从属。
exchange operator
用于自动并行化的标准算子。并行有两类,一类是将数据分成很多分区,每个分区执行一样的operator树,这些执行是并行的,最后结果合并起来;一类是数据只有一份,要执行的各个算子之间可以并行。
Volcano中,并行化operator交互使用的就是exchange operator,不管是单机或者多机。exchange 可以理解成一个异步信号,当这个信号发出后,接收方就开始干活,发送方不会停下来等待结果,而是继续前进,当接收方干完活返回结果的时候,发送方按照预定逻辑处理就可以了。
Volcano Model
Volcano Model是一种经典的基于行的流式迭代模型(Row-BasedStreaming Iterator Model),主流关系数据库中都采用了这种模型。
在Volcano模型中,所有的代数运算符(operator)都被看成是一个迭代器,它们都提供一组简单的接口:open()—next()—close(),查询计划树由一个个这样的关系运算符组成,每一次的next()调用,运算符就返回一行(Row),每一个运算符的next()都有自己的流控逻辑,数据通过运算符自上而下的next()嵌套调用而被动的进行拉取。
火山模型中每一个运算符都将下层的输入看成是一张表,next()接口的一次调用就获取表中的一行数据,这样设计的优点是:
- 每个运算符之间的代数计算是相互独立的,并且运算符可以伴随查询关系的变化出现在查询计划树的任意位置,这使得运算符的算法实现变得简单并且富有拓展性。
- 数据以row的形式在运算符之间流动,只要没有sort之类破坏流水性的运算出现,每个运算符仅需要很少的buffer资源就可以很好的运行起来,非常的节省内存资源。
但是这种运算符的嵌套模型也有它的缺点:
- 火山模型的流控是一种被动拉取数据的过程,每行数据流向每一个运算符都需要额外的流控操作,所以数据在操作符之间的流动带来了很多冗余的流控指令。
- 运算符之间的next()调用带来了很深的虚函数嵌套,编译器无法对虚函数进行inline优化,每一次虚函数的调用都需要查找虚函数表,同时也带来了更多的分支指令,复杂的虚函数嵌套对CPU的分支预测非常不友好,很容易因为预测失败而导致CPU流水线变得混乱。这些因素都会导致CPU执行效率低下。
一个查询的直接开销主要取决于两个因素:第一个因素就是跨存储和计算操作符之间的数据传输开销
,另一个因素就是数据运算所花费的时间
。
推送模型
拉取&推送模型融合
事务
共享锁(S锁,读锁)
若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。这保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。
排他锁(X锁,写锁)
若事务T对数据对象A加上X锁,事务T可以读A也可以修改A,其他事务不能再对A加任何锁,直到T释放A上的锁。这保证了其他事务在T释放A上的锁之前不能再读取和修改A。
Volcano Optimizer
将搜索分为两个阶段,在第一个阶段枚举所有逻辑等价的 Logical Algebra,而在第二阶段运用动态规划的方法自顶向下地搜索代价最小的 Physical Algebra
Cascades Optimizer
则将这两个阶段融合在一起,通过提供一个 Guidance 来指导 Rule 的执行顺序,在枚举逻辑等价算子的同时也进行物理算子的生成,这样做可以避免枚举所有的逻辑执行计划
Reference: