而从目前实际的发展来看,基本上目前发展的核心思路并没有绕开人们在数据库理论领域内所积累的那些关键的特性。因此,如果你希望能够快速的在海量数据的在线处理领域内积累知识,从传统数据库领域入手是绝对不会错的。
下面,就让我们对数据库做个简单的解刨,看看数据库里面有哪些核心的组件吧。
映射(Map):
首先就需要有能够存储数据并提供查询的结构,这个结构,在java里面就是Map。C里面也是Map.他的核心作用就是,建立一种key与value的映射关系,当给定某个key的时候,他能够返回这个key所对应的value给用户。这是用户在进行查询时的主要数据结构。
预写式日志(write-aheadlogging,WAL):
就是个队列,记录了你每一次写的操作。自然而然的,因为你的每次写操作都被记录下来了,所以就算计算机断电了,只要这个日志没有损坏,计算机重启后按照这个log,重放在断电时的那些写操作,就可以保证你的数据不丢。
这里,一定会有人问:既然我数据都存储在k-v表里了,明显就不会丢失了。为什么还要有这个log呢?这其实就是一个计算机的本质性问题了,别看现代计算机运算速度这么快,他终归也只是个“图灵机”实现,或者更具象化一点,就是一台打字机,一次只能打一个字母,那么可能会有人问了,如果我要用几个字母来表示同一个意思,应该怎么做呢?在英语中,最简单的方式就是在词组和词组之间增加空格。比如writeaheadlogging.就是三个由字母组成的单词。在计算机里,也有类似的问题,用户的一次写入操作,可能对应计算机内的多步操作,如何能够保证这多次的操作要么全部成功,要么全部失败呢?WAL就是个解决的方法,他利用的是操作系统里的一个原子操作fsync().该操作的作用是将一小段数据写入到磁盘,从而保证数据不会丢失。
我们来看一下整体的操作思路:记录用户的写入操作(insert,update,delete)->进行内部多次key-value映射的构建,包括主数据,辅助索引数据等->标记该用户操作完成。
触发器(trigger)
一个不难理解的概念,当发生insert,update,delete等操作的时候,可能会有一些需求需要依托这些操作而被触发执行其他的操作。比如每一行针对表A的更新,都会引发B表内的更新。那么这个“引发”的过程,就是触发器。在一些其他的语言里面,这也被叫做callback,IFTTT,Listener等。但核心概念都一样,被动的因为某个事件而触发一段代码逻辑的运行。
在一些数据库的实现中,甚至二级索引的更新也是使用触发器来完成的哦:)
在数据库内,触发器全部是同步实现的,也就是说,只有当数据写入的操作,以及触发器的操作全部都执行完成后,才会返回用户执行成功。
锁(lock)
锁的主要目标是允许线程圈定一批资源,并规定该资源只允许发出圈定请求的那个线程进行访问,而其他线程则必须等待。
这个概念产生的主要原因其实还是与计算机是图灵机有关。。本来计算机就是台图灵机,一个时钟周期内只能打一个字母,但这样他就很难同时做好几件事情,比如听着歌写代码,这件事其实从计算机硬件来说是做不到的,他只能模拟,利用时分复用的方式,把cpu的运算分解成小片,每个线程都只占用一小段时间,从而能够做到同一时间做好几件事。但是,想一想,如果我们希望一个人A用打字机打iamgod.而希望另外一个人B用同一台打字机打pigismoney.开始,时间片分配给A,他打印了iam后,A被cpu换出,B被换入,打印了pig后被其他人换出,那么我们自然就发现。。数据就变成了。。。那么锁的作用就是保证一个逻辑的原子操作没有完结的时候,这张打印纸只属于A,其他人不能对其进行访问或进行修改。
明白了原理,来简单看看实现,锁主要是由排他锁(写锁)和共享锁(读锁)构成,在数据库的锁实现中,有很多针对共享锁和排他锁相互组合的细节性描述,但其核心的问题却永远没变:
1)尽可能的减少同一时间内被阻塞的线程数,从而提升并行度。
2)尽可能的避免死锁
可以说数据库实现的是好是坏,关键就看着锁的优化好不好,这在分布式场景或者在单机内都是最重要的一个机制。
执行优化器
这是关系数据库得名的原因,主要的作用是将关系查询转换成key-value查询,输入是sql的抽象语法树(ast),输出则是执行计划,就是各位在数据库命令行打explainsql时候出来的那些东西。
理解上很简单,但实际上实现起来却是最为复杂的,在上个世纪,大部分的执行优化器使用rulebasedoptimizer,也就是基于规则的优化,但在现代数据库实现中,大部分的优化器都采取了costbasedoptimizer了,他们之间最大的不同,就是cbo更多的考虑了数据实际的区分度情况,从而能更简单准确的从。多个可选的索引中选择一个正确的索引。
sql解析器
作用很简单,把用户输入的sql转化为计算机可以理解的抽象语法树(不懂就去看编译原理:)
好了,基本组件儿介绍完毕,下面我们利用这些核心组件来尝试拼装一些外围的概念。