本文整理自DTCC2016主题演讲内容,录音整理及文字编辑IT168@杨璐。如需转载,请先联系本公众号获取授权!
演讲嘉宾
姜瑞海
山东瀚高数据库首席内核架构师
现任山东瀚高基础软件股份有限公司瀚高数据库首席内核架构师,负责主持瀚高数据库集群的设计和研发工作,同时参与瀚高大数据处理平台的设计。在数据库内核开发和数据库集群开发方面有丰富的经验。 姜瑞海有近20年的数据库及相关软件开发经验,曾经供职于朗讯、IBM等多个跨国高科技企业,从事软件研发工作,参与过多个大型跨国软件项目研发。后期加入山东瀚高基础软件股份有限公司,从事数据库架构设计和内核研发。
分享内容
我今天的内容主要分为三部分:
1、国产数据库心路历程
2、基于PostgreSQL 分析数据库内核工作原理
3、修改PostgreSQL内核 扩展新功能
国产数据库之路,也许很多人没有太多考虑。我们深究这个问题多年,自行研发,困难和风险很大,因为很难做到足够严谨和稳定。最难的是能否长时间保持持续领先。比自行研发更好的办法是通过收购、并购的方法引入一个成熟的数据库产品,但存在技术上二次落后的风险。因此,最终的办法就是开源发展,基于开源数据库。
PostgreSQL开发入门
如果说你对于PostgreSQL感兴趣,并且准备去研究它的话,推荐一个网站(如上图),在这个网站下,你可以找到它的源代码,找到它的各种文档,查询它的各种问题的全面解答,甚至向它的Email列表里提问。
PostgreSQL,它主要是用C语言写的,所以你可能需要有Source Insight/ cscope/eclipse
这些工具。再一个在Linux下编译部署和运行PostgreSQL相对比较方便。我们常用的CentOS/Ubuntu你可能需要熟悉Linux的编程。Flex和Bison,它们分别是词法分析器和语法分析器的生成工具。所以你有必要去了解这两个工具。使用gdb单步执行跟踪PostgreSQL的代码,它是高效的,非常高效精确的理解PostgreSQL代码有效的手段。
PostgreSQL数据库内核架构
PostgreSQL是一个服务器端程序,一个客户端程序要想给服务器通讯的时候,它首先要建立一个链接。我们有一个后台进程,叫Postmaster。它是负责建立这样一个链接的。当客户端和Postmaster之间建立一个链接之后,会启动一个子进程。这个子进程负责后续所有的工作。一个SQL查询从客户端发送到服务器上的时候,实际上发送到了postgres backend上,由postgres backend经过几个核心的处理。第一个叫SQL Compiler语法词法分析模块;第二块是Query Rewriter,它是重写模块;第三个核心模块是Plan Optimizer,这是查询优化模块;第四个核心是Plan Executor,也就是计划执行的模块。
这四个核心的模块,它包含了大部分的关系数据库的理论。当然数据库的内核还有很多其他的内容,比如说事务管理,日志管理等等,但是这些可能好多都属于常规的编程,而我们今天重点讨论这四个模块。
关于计划的执行模块 单表扫描,SequentialScan
如果一个简单的SQL语句select*,从这个表里面获取一行,这个条件是select * from contact where telephone=“2222”;对于这个简单的查询,进入到PostgresQL之后,经过了核心,一二三的处理,最后到达了我们的核心四,在第四个核心内部,它有可能是要用一个SequentialScan,也就是说,顺序扫描的算法来实现对整个表的扫描。比如说它从第一行扫描到最后一行。每当遇到一个符合条件的行它就输出,这个SequentialScan实际上是单个表的扫描。
关于计划的执行模块 单表扫描,IndexScan
IndexScan,也就是基于索引的扫描。同样的一个SQL语句进来之后,postgreSQL也可以用IndexScan来扫描。要使用基于索引的扫描,前提条件是要创建一个索引。索引里面每一条数据对应着我们表里面的每一条数据。索引数据的组成主要是有telephone number+当前位置信息,索引数据在索引里面是以排序的方式保留的。而这个排序的Key值就是telephone number,当一个查询执行的时候,的值在索引里面快速的找到相应的索引。然后根据索引潜在的行信息,直接进入到数据行。IndexScan还有刚才介绍的Sequential Scan,都是可以完成对单个表的扫描。在postgreSQL处理的过程当中,它到底是使用哪种扫描方式呢?这个是由我们的第三核心,也就是查询优化器来实现这个功能的。它的基本思想是计算两个扫描算法的代价,选择最小的一个。
接下来我们介绍这两个表的扫描计划:
这个算法,我们这里用了一个算法叫做Nested Loop Join,嵌套的循环。首先从外表里面读到一条数据,根据Key值,从内部表里面读取一条数据,匹配它俩是否符合条件,如果符合条件,基本上两行数据连接成一条数据输出。然后Nested Loop Join继续对列表扫描,直到把所有的列表扫描完毕,截止到它读取外表的第二条数据,然后再继续对列表扫描,所以这是一个两层循环。我们实际上用了三个算法,下面两个算法分别都是顺序扫描算法,这是一个典型的树形结论,通常叫做计划树。
接下来这个例子也是对两个表实现扫描Join,但是它使用了一个叫Hash Join的算法。它需要把内表通过扫描的方式读进来,然后建立Hash表,读取外表的一条数据,通过这个Key值,在Hash里面以较快的速度查找到,继续查找,直到把Hash所有的数据找到之后,他再读外表的一条数据,读进来,再继续查询解析。
如果join三个表,甚至更多表的情况应该怎么办呢?(如下图)
其基本思想是:先join2个表,join的结果再和另外一个单表做join,以此类推,直到所有的表都连接在一起。
SQL请求的处理步骤:概览
SQL请求的处理步骤:1-parser
主要是根据SQL的语法来做分析,来做编译。输入的SQL语句变成一个数据结构,通常称为Raw Parse Tree,例如SelectStmt。
SQL请求的处理步骤:2-rewriter
重写部分的工作,最主要的包括:重写视图和应用规则。重写视图是根据视图的定义,把视图转换为一个子查询。例如View2对应转换成子查询:select*from Tab2 Tab3...
而规则的举例如下:建立一个规则log_shoelace,这个规则是监控表shoelace_data,当这个字段sl_avail发生变化的时候,就要向一个log表里面形成记录,这是一个规则。当一个查询进来的时候rewriter,判断这个列上面有没有定义规则。如果有规则,则应用规则对应的SQL语句。这样,一个update语句进来后,被转换成为2条语句:一个update,加上一个insert。
SQL请求的处理步骤:3-planner
它主要工作就是生成一个查询计划树,它对单表选择一个合适的扫描算法,对多表join选择合适的join算法,对选择扫描算法和join算法,他大体的思想也是根据代价来做处理的,哪一个代价最小选择哪一个。最终选择哪一个算法,还是要考虑到这个查询最终生成多少数据。
举几个例子,展示了posgreSQL一个完整的生成查询计划树的过程。
这是一个比较典型的例子。首先我们看一下从三个表里面,A表、B表、C表里面读取数据,它的条件是A表等于B表,B表等于C表。posgreSQL在生成计划的时候,它首先列举每个表的扫描方式。从众多的扫描方式当中找到一个代价最小的。对应多标查询,我们先对单个表的所有查询计划做估计,找到每个单表的最优查询计划;然后逐层向上,找到每一层最优的Join组合,直到最上层。
如果参与的表很多(例如超过10个表),上面的搜索方式会因为计算量很多而不实用。这种情况下,我们往往在每一层上搜索一定数量的组合,找到最优的,而不是遍历所有的组合。
SQL请求的处理步骤:4-executor
如果一个查询计划树生成了,接下来第四个核心,执行的过程当中。在计划执行的时候,首先要有这个计划执行树,这个树形结构。计划树的节点有各个operator组成,Operator彼此连接,底部的Operator为上层的Operator提供输入。数据的读取/产生是由顶部按需上拉驱动的:从树根开始,上层节点调用其子节点获取子节点的下一条数据,子节点调用子节点,直到最底层节点。
最底层的节点往往是读取物理数据表,例如:sequential scans , index scans
上层的节点基本是join nodes节点: nested-loop joins, merge joins, hash joins . 每一个join node合并两个输入数据流,成为一个数据流。
PostgreSQL内核总结
一个进程运行的过程当中,如果一个查询语句进来之后,它会经过parser的分析,最后到这一步,这个语句是DDL还是DML。如果是DDL,比如说是CREATETABLE这样的语句,是进入一个特殊的分支处理,processUtility();如果是DML,会走核心2、核心3、核心4的处理。
修改PostgreSQL内核,扩展新功能
如果说你把内核研究透了,这仅是一部分。最有价值的是对它做修改、加强。
PostgreSQL集群方案实现了数据库系统在读、写两方面的水平扩展。通过扩展节点的数量来极大程度的提高数据库系统的计算能力和存储能力。这样的话,可以通过较为低价的PC服务器,来组建处理能力到存储能力极强大的集群,它可以代替你的小机。
在数据库集群解决方案里,有一种节点叫做协调器,它是负责处理来自客户端会话,另外一种节点叫数据节点,它是保存用户数据的,一个用户表,比如说A,它可以在不同的数据节点之间分片保存,比如说A表,它分了四片,分别保存在四个不同的节点上。有一些表,像B表,它可以在不同的节点之间保存相同的备份,具体怎么分配,是由你的业务策略来决定的,你想在哪方面做提高和优化。
当一个客户端连到我们服务器的时候,实际上它连的是一个协调节点,它并不知道后台的数据节点,也不知道其他的节点,它发送一个SQL查询,到一个协调节点上,协调节点再生成计划的时候,生成查询计划的时候,它会考虑相关的表在数据节点的分布情况,生成了它的本地的产品计划,以及远端的产品计划,如果说本地的产品计划,它们在协调节点上运行,如果是远端的产品计划,它会分发到各个数据节点上去执行,数据节点执行完产品计划,得到的数据之后,返回给协调节点。最后协调节点把数据返回来。
PosgreSQL集群,在实现的时候,它的内核做了很大的修改,左边是一个标准的PostgreSQL,而右边是协调节点+数据节点。我们的协调节点,它做了很大的改动,它把Planner都改动了,就是生成计划的时候,它要考虑到数据在数据节点之间的分布情况。而执行器,同样它可以执行本地的计划,也可以远程遥控执行远端的计划。
PostgreSQL与其他数据源协同工作
使用FDW,FDW的全称是FOREIGN DATA WRAPPER,FDW的作用是把PosgreSQL服务器外边的各种数据源,在我的PosgreSQL上建立一个虚拟表,叫做FOREIGN TABLE。我的程序通过标准的SQL语句,通过访问PosgreSQL,来访问这个外表,从而访问到外部的数据。
目前有很多很多的这种FDW被设计出来,我自己简单搜了一下,有接近一百种,也就是说,你想到了各种的数据基本上都是这样的。如果你用的时候,你可以下载一个,如果这个满足不了你的需要,你可以自己设计一个自己的FDW。
在设计FDW的时候,PosgreSQL它有规范,你只需要按照它的规范,实现一些相应的函数,另外要实现对外部数据源访问的函数。PosgreSQL的FDW规范要实现最多的函数是与Planner相关的函数,其次就是生成计划相关的函数。再一个与计划执行的时候相关的函数。
最后,介绍结束。如有任何问题,欢迎来信交流!
关于DTCC
中国数据库技术大会(DTCC)是目前国内数据库与大数据领域最大规模的技术盛宴,于每年春季召开,迄今已成功举办了七届。大会云集了国内外顶尖专家,共同探讨MySQL、NoSQL、Oracle、缓存技术、云端数据库、智能数据平台、大数据安全、数据治理、大数据和开源、大数据创业、大数据深度学习等领域的前瞻性热点话题与技术,吸引IT人士参会5000余名,为数据库人群、大数据从业人员、广大互联网人士及行业相关人士提供了极具价值的交流平台。