深入理解传统集中式数据库架构

概述

数据库是“按照数据结构来组织、存储和管理数据的仓库”。是一个长期存储在计算机内的、有组织的、可共享的、统一管理的大量数据的集合。

数据库系统把每一个应用只定义并且维护自己的数据的这样一种形式(图1)改变为对于数据的集中定义和集中管理(图2)。这种新的变化带来了数据独立性(data independence), 使得应用程序不再受到数据在逻辑组织或物理组织上带来的变化影响,反之亦然。

使用数据库系统的动机之一就是集成企业的运营数据,提供集中的、对于数据可控制的存取。

传统集中式数据库架构

将数据库系统拆开来看,其核心模块包括应用接口、SQL接口、查询执行引擎、数据访问模块和存储引擎。其中,查询执行引擎进一步可以拆分为计划生成器、计划优化器和计划执行器;数据访问模块则可以分为事务处理、内存处理、安全管理以及文件和索引管理等模块;并且事务处理是最核心的模块,其中包括了崩溃恢复和并发控制;最底层的存储引擎则包括数据文件、索引文件和系统及元数据文件。

传统数据库架构大致划分为几层:

  • 应用层: 给客户端提供连接数据库的工具。
  • 会话层: 处理客户端与服务器的session信息,并检测是否有访问数据库的权限相关的权限动作。
  • 计划层: 解析SQL字符串和逻辑计划的生成。
  • 计算层: 把逻辑计划转成物理计划,并计算结果。
  • 数据访问层: 文件和索引、事务的管理。
  • 存储引擎: 外部数据源存储的数据文件。

下面举一个查询的例子,看各个层之间是如何配合实现的。

1. 查询分析处理过程

数据库查询分析处理过程是这样的:首先,通过SQL语句将查询任务提交上来,之后经过Session Manger和Parser进行处理,此时会有各种各样的执行方式,并生成Catalog和逻辑执行计划;之后对于逻辑执行计划进行优化,并生成物理执行计划;之后在借助系统的统计信息,如索引管理、内存管理来生成一个优化后的物理执行计划,再执行并生成最后结果。

简单而言,数据库系统的架构就是持久化存储的数据按照Data Page的形式进行存储,这些数据块在查询访问的时候会被带到内存里面。系统中有内存池,每个内存池可以装载一个Page,此时的问题就是内存池的大小是有限的,如果数据存储非常大,需要进行优化。此外,还涉及到优化数据访问的问题,一般通过索引解决,主要是Hash索引和树形索引。那么我们把各个组件拆解,来描述下各个组件都使用什么样的技术。

2. 核心技术

2.1 JDBC组件

大部分应用场景下,使用的都是JDBC组件,那么JDBC是什么?JDBC (Java Database Connectivity) API,即Java数据库编程接口,是一组标准的Java语言中的接口和类,使用这些接口和类,Java客户端程序可以访问各种不同类型的数据库。比如建立数据库连接、执行SQL语句进行数据的存取操作。通常和客户应用绑定在一起。

2.2 会话管理组件

在连接数据库与断开连接之间的时间被称为一个数据会话。会话管理通常是数据库和外部交互的组件。JDBC与会话管理通常数据交互,可以使用grpc或者thrift这种RPC通信的技术。

2.3 权限管理组件

权限是用户对一项功能的执行权利,在数据库中,根据系统管理方式的不同,可将权限分为系统权限与对象权限两类, 系统权限是指被授权用户是否可以连接到数据库上及数据库中可以进行哪些系统操作,另一类是对象权限是指用户对数据库中具体对象所拥有的权限, 对象权限,如数据库中的表,视图,存储过程,存储函数等。权限模组一般采用的技术是缓存机制。因为用户权限相关的数据会被持久化到物理设备中。缓存机制可以有效的减少IO操作。

2.4 SQL解析器组件

数据库需要支持标准的 SQL 语言,具体实现的时候必然要涉及到词法分析和语法分析。早期的程序可能会优先考虑手工实现词法分析和语法分析,现在大多数场合下都会采用工具来简化实现。MySQL、PostgreSQL 等采用 C/C++ 实现的开源数据库采用的是现代的 yacc/lex 组合,也就是 GNU bison/flex。其他比较流行的工具还有 ANTLR、JavaCC 等等。这些工具大多采用扩展的 BNF 语法,并支持很多定制化选项,使得语法比较容易维护和实现。通过这些工具可以使应用端发送过来的SQL字符串,转成一个AST(Abstract Syntax Tree)抽象SQL语法树。

2.5 SQL查询优化器

优化器作为数据库核心功能之一,也是数据库的“大脑”,理解优化器将有助于我们更好地优化SQL。

传统关系型数据库里面的优化器分为CBO和RBO两种。

RBO(Rule Based Potimizer) 基于规则的优化器:

RBO :RBO所用的判断规则是一组内置的规则,这些规则是硬编码在数据库的编码中的,RBO会根据这些规则去从SQL诸多的路径中来选择一条作为执行计划(比如在RBO里面,有这么一条规则:有索引使用索引。那么所有带有索引的表在任何情况下都会走索引)所以,RBO现在被很多数据库抛弃(oracle默认是CBO,但是仍然保留RBO代码,MySQL只有CBO)

RBO最大问题在于硬编码在数据库里面的一系列固定规则,来决定执行计划。并没有考虑目标SQL中所涉及的对象的实际数量,实际数据的分布情况,这样一旦规则不适用于该SQL,那么很可能选出来的执行计划就不是最优执行计划了。

CBO(Cost Based Potimizer) 基于成本的优化器:

CBO :CBO在会从目标诸多的执行路径中选择一个成本最小的执行路径来作为执行计划。这里的成本他实际代表了MySQL根据相关统计信息计算出来目标SQL对应的步骤的IO,CPU等消耗。也就是意味着数据库里的成本实际上就是对于执行目标SQL所需要IO,CPU等资源的一个估计值。而成本值是根据索引,表,行的统计信息计算出来的。(计算过程比较复杂)

2.6 物理执行器组件

物理执行器又叫做计划执行器。执行器架构一般采用Volcano Model经典的基于行的流式迭代模型(Row-BasedStreaming Iterator Model)。比如我们熟知的主流关系数据库中都采用了这种模型,例如Oracle,SQL Server, MySQL等。

在Volcano模型中,所有的代数运算符(operator)都被看成是一个迭代器,它们都提供一组简单的接口:open()—next()—close(),查询计划树由一个个这样的关系运算符组成,每一次的next()调用,运算符就返回一行(Row),每一个运算符的next()都有自己的流控逻辑,数据通过运算符自上而下的next()嵌套调用而被动的进行拉取。

这是一个最简单的火山模型例子,拉取数据的控制命令从最上层的Output运算符依次传递到执行树的最下层,而数据流动的方向正好相反。

这种计算模型对于CPU Cache是不友好的,所以一般在做表达式计算的时候通常采用编译执行。来提高CPU Cache的命中率。通常采用的的技术是Llvm技术。

对于OLAP数据的物理执行引擎通常采用列存的数据结构,因为可以比较高效的提高CPU Cache的利用率,通常也可以采用比较高效SIMD指令来处理。

随着各个商业数据库做软硬件一体化解决方案的产生,通常也会使用GPU和FPGA这种技术来提高计算性能。

2.7 索引组件

索引是对数据库表中一列或多列的值进行排序的一种结构,使用索引可快速访问数据库表中的特定信息。如果想按特定职员的姓来查找他或她,则与在表中搜索所有的行相比,索引有助于更快地获取信息。

索引的一个主要目的就是加快检索表中数据,亦即能协助信息搜索者尽快的找到符合限制条件的记录ID的辅助数据结构。

数据库索引的核心技术总的来说,索引就是拿空间换时间。数据库技术和大数据技术会有一个融合的过程,除了前面讲到的B树索引、Hash索引等,还有倒排索引、MinMax索引、BitSet索引、MDK索引等。

2.8 事务处理组件

数据库的事务处理是数据库最重要的核心模组之一,数据库事务(transaction)是访问并可能操作各种数据项的一个数据库操作序列,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。事务由事务开始与事务结束之间执行的全部数据库操作组成。

  • 事务是并发控制的基本单位。
  • 一个事务包含的诸操作要么都执行,要么都不执行。

事务的属性

  • 原子性 :事务是数据库的逻辑工作单位,一个事务的诸操作要么都做,要么都不做。
  • 一致性 :指事务执行前后必须保持数据库的逻辑一致性。一致性和原子性是密切相关的。
  • 隔离性 :指并发执行的各个事务之间不能互相干扰。
  • 持久性 :指一个事务的操作提交后, 其对数据库的改变是永久的,属于物理的而非逻辑的。

数据库的事务隔离级别

  • READ UNCOMMITTED(读未提交数据):允许事务读取未被其他事务提交的变更数据,会出现脏读、不可重复读和幻读问题。
  • READ COMMITTED(读已提交数据):只允许事务读取已经被其他事务提交的变更数据,可避免脏读,仍会出现不可重复读和幻读问题。
  • REPEATABLE READ(可重复读):确保事务可以多次从一个字段中读取相同的值,在此事务持续期间,禁止其他事务对此字段的更新,可以避免脏读和不可重复读,仍会出现幻读问题。
  • SERIALIZABLE(序列化):确保事务可以从一个表中读取相同的行,在这个事务持续期间,禁止其他事务对该表执行插入、更新和删除操作,可避免所有并发问题,但性能非常低。

原子性使用的核心技术

Transaction Undo Log来保证数据的原子性。日志的作用能够在发生错误时撤销之前的全部操作,肯定是需要将之前的操作都记录下来的,这样在发生错误时才可以回滚。

回滚日志除了能够在发生错误或者用户执行 ROLLBACK 时提供回滚相关的信息,它还能够在整个系统发生崩溃、数据库进程直接被杀死后,当用户再次启动数据库进程时,还能够立刻通过查询回滚日志将之前未完成的事务进行回滚,这也就需要回滚日志必须先于数据持久化到磁盘上,是我们需要先写日志后写数据库的主要原因。

一致性使用的核心技术

数据库一致性(Database Consistency)是指事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。保证数据库一致性是指当事务完成时,必须使所有数据都具有一致的状态。通常需要使用一致性协议来保证例如Paxos、Raft。

隔离性使用的核心技术

锁、时间戳、MVCC来保证数据的隔离性。

锁是一种最为常见的并发控制机制,在一个事务中,我们并不会将整个数据库都加锁,而是只会锁住那些需要访问的数据项,常见数据库中的锁都分为两种,共享锁(Shared)和互斥锁(Exclusive),前者也叫读锁,后者叫写锁。读锁保证了读操作可以并发执行,相互不会影响,而写锁保证了在更新数据库数据时不会有其他的事务访问或者更改同一条记录造成不可预知的问题。

时间戳也是实现事务的隔离性的一种方式,使用这种方式实现事务的数据库,例如 PostgreSQL 会为每一条记录保留两个字段;读时间戳中包括了所有访问该记录的事务中的最大时间戳,而记录行的写时间戳中保存了将记录改到当前值的事务的时间戳。使用时间戳实现事务的隔离性时,往往都会使用乐观锁,先对数据进行修改,在写回时再去判断当前值,也就是时间戳是否改变过,如果没有改变过,就写入,否则,生成一个新的时间戳并再次更新数据,乐观锁其实并不是真正的锁机制。

MVCC也是实现事务的隔离性的一种方式,通过维护多个版本的数据,数据库可以允许事务在数据被其他事务更新时对旧版本的数据进行读取,很多数据库都对这一机制进行了实现;因为所有的读操作不再需要等待写锁的释放,所以能够显著地提升读的性能,MySQL 和 PostgreSQL 都对这一机制进行自己的实现。

持久性使用的核心技术

Transacation Redo Log来保证数据的持久性。日志由两部分组成,一是内存中的重做日志缓冲区,因为重做日志缓冲区在内存中,所以它是易失的,另一个就是在磁盘上的重做日志文件,它是持久的。

2.9 存储引擎组件

存储引擎通常要处理的事情

  • 并发性:某些应用程序比其他应用程序具有很多的颗粒级锁定要求(如行级锁定)。
  • 事务支持:并非所有的应用程序都需要事务,但对的确需要事务的应用程序来说,有着定义良好的需求,如ACID兼容等。
  • 引用完整性:通过DDL定义的 外键,服务器需要强制保持关联数据库的引用完整性。
  • 物理存储:它包括各种各样的事项,从表和索引的总的页大小,到存储数据所需的格式,到物理磁盘。
  • 索引支持:不同的应用程序倾向于采用不同的索引策略,每种存储引擎通常有自己的编制索引方法,但某些索引方法(如B-tree索引)对几乎所有的存储引擎来说是共同的。
  • 内存高速缓冲:与其他应用程序相比,不同的应用程序对某些内存高速缓冲策略的响应更好,因此,尽管某些内存高速缓冲对所有存储引擎来说是共同的(如用于用户连接的高速缓冲,MySQL的高速查询高速缓冲等),其他高速缓冲策略仅当使用特殊的存储引擎时才唯一定义。
  • 性能帮助:包括针对并行操作的多I/O线程,线程并发性,数据库检查点,成批插入处理等。
  • 其他目标特性:可能包括对地理空间操作的支持,对特定数据处理操作的安全限制等。

来源:肉眼品世界

举报
评论 0