关系型数据库原理

这篇文章是对知乎上如何本身实现一个关系型数据库的一个尝试性回答,后续会不断更新。mysql

对外数据模型为关系型数据库,内部的实现主要分红两大类,一类是disk-based,好比mysql,postgres,一类是memory based,后者包括MemSQL,SAP HAHA,OceanBase。这里说一个disk-based的关系型数据库涉及多少东西。算法

上世纪70/80年代内存不大,数据不能都放在内存里,大部分数据都存在磁盘上,读数据也须要从磁盘读,然而读写磁盘太慢了,因此就在内存里作了一个buffer pool,将已经读过的数据缓存到buffer pool中,写的时候也是写到buffer pool中就返回,buffer pool的功能就是管理数据在磁盘和内存的移动。在buffer pool中数据的管理单位是page。page大小通常几十KB。通常均可以配置。若是buffer pool中没有空闲的page,就须要将某一个page提出buffer pool,若是它是dirty page,就须要flush到磁盘,这里又须要一个LRU算法。一个page包含多条记录,page的格式须要设计用来支持变长字段。若是这时宕机了,buffer pool中的数据就丢了。这就须要REDO log,将对数据的修改先写到redo log中,而后写buffer pool,而后返回给客户端,随后,buffer pool中的dirty page会被刷到数据文件中(NO FORCE)。那么重启的时候,数据就能从redo log中恢复。REDO log还没刷完就刷数据到磁盘能够加快写入速度,缺点就是恢复的时候须要回放UNDO log,回滚一些尚未提交的事务的修改。写log又分为逻辑log和物理log,还有物理逻辑log。简单说逻辑log就是记录操做,好比将某个值从1改为2.而物理log记录具体到record的位置,例如某个page的某个record的某个field,原来的值是多少,新值是多少等。逻辑log的问题是并发状况下不太好恢复成一致。物理log对于某些操做好比create table又过于琐碎,因此通常数据库都采用混合的方式。为了跟踪系统中各类操做的顺序,这就须要为log分配id,记作LSN(log sequence number)。系统中记录各类LSN,好比pageLSN, flushedLSN等等。为了加快宕机恢复速度,须要按期写checkpoint,checkpoint就是一个LSN。
以上ACID里的C和D有关。下面说A和I,即原子性和隔离性。sql

这两个性质经过concurrency control来保证。隔离级别有不少种,最开始有4种,从低到高read uncommitted, read committed, repeatable read, serializable。serializable就是多个事务并发执行的结果和某种顺序执行事务的结果相同。除了serializable,其余都有各类问题。好比repeatable read有幻读问题(phantom),避免幻读须要gap lock。read committed有幻读和不可重复读问题。后来又多了一些隔离级别,好比snapshot isolation,snapshot isolation也有write skew问题。早期,并发控制协议大可能是基于两阶段锁来作的(2PL),因此早期只有前面提到的四种隔离级别,后来,又出现一类并发控制协议,统称为Timestamp Ordering,因此又多了snapshot isolation等隔离级别。关于隔离级别,能够看看这篇A Critique of ANSI SQL Isolation Levels。2PL须要处理deadlock的问题。数据库

Timestamp Ordering大致的思想就是认为事务之间冲突不大,不须要加锁,只在commit的时候check是否有冲突。属于一种乐观锁。
Timestamp Ordering具体来讲包括多种,最多见的MVCC就是这类,还有一类叫作OCC(optimistic concurrency control)。MVCC就是对于事务的每次更新都产生新的版本,使用时间戳作版本号。读的时候能够读指定版本或者读最新的版本。几乎主流数据库都支持MVCC,由于MVCC读写互相不阻塞,读性能高。MySQL的回滚段就是用来保存老的版本。MVCC须要有后台线程来作再也不须要的版本的回收工做。Postgres的vacuum就是作这事的。OCC和MVCC的区别是,OCC协议中,事务的修改保存在私有空间(好比客户端),commit的时候再去检测冲突,一般的作法是事务开始时看一下本身要修改的数据的最后一次修改的时间戳,提交的时候去check是否这个时间戳变大了,若是是,说明被别人改过了,冲突。冲突后能够回滚或者重试。缓存

上面这些搞定了就实现了数据库的核心,而后为了性能,须要index,一般有两种,一种支持顺序扫描B+Tree,还有一种是Hash Index。单条读适合用Hash Index,O(1)时间复杂度,顺序扫描只适合用B+Tree,O(logN)复杂度。而后,有些查询只须要扫描索引就能获得结果,有些查询直接扫描数据表就能获得结果,有些查询能够走二级索引,经过二级索引找到数据表而后获得结果。。具体用哪一种方式就是优化器的事了。性能优化

再外围一些,关系型数据库天然须要支持SQL了,由SQL变成最后能够执行的物理执行计划中间又有不少步,首先SQL经过词法语法分析生成抽象语法树,而后planner基于这棵树生成逻辑执行计划,逻辑执行计划的生成一般涉及到等价谓词重写,子查询消除等逻辑层面的优化技术,优化的目的固然是性能。好比等价谓词重写,用大于小于谓词消除like,between .. and..等不能利用索引的谓词。下一步是逻辑执行计划生成物理执行计划,物理执行计划树每一个节点是一个operator,operator的执行就是实实在在的操做,好比扫表的operator,filter opertor。一个逻辑执行计划一般能够有多个物理执行对应,选择哪一个就涉及到物理执行计划优化,这里涉及到经典的cost model,综合考虑内存,CPU, I/O,网络等。最典型的,三表join,从左到右仍是右到左,使用hash join,仍是sort merge join等。关于查询优化器能够参考数据库查询优化器的艺术:原理解析与SQL性能优化网络

相关文章
相关标签/搜索