存储引擎是数据库的核心组件,存储引擎的一些特性决定了数据库的一些基本性能特性。存储引擎的选择也是数据库开发人员十分谨慎的一个方面。以前我也在一些文章里介绍了一些常见的存储引擎,也对这些存储引擎的优缺点做了概括。不过如果没有真实的去使用,去匹配应用负载,仅仅是纸上谈兵,是完全不够的。
目前数据库中使用最为广泛的存储引擎不外乎两个系列三种引擎,分布式数据库喜欢采用的LSM-TREE存储,以及普通通用型数据库常用的BTREE/HEAP存储。BTREE和HEAP存储是两种十分相近的存储引擎结构,Oracle,PostgreSQL,MySQL等数据库都使用BTREE/HEAP结构。BTREE是一个泛称,实际上数据结构是一棵增强的B+TREE。
这是一张来自网上的两大阵营的一些数据库产品的清单,我们可以看出传统数据库使用B-TREE结构的比较多,而一些新兴的数据库系统往往使用LSM-TREE。LSM-TREE是Log-Structured Merge Tree的简称,是一种典型的不可变结构的存储引擎,其目的是为了降低可变结构存储引擎中写入数据需要先找到已经存在的PAGE,然后再进行修改的成本,从而实现更高并发的写入。因此LSM-TREE引擎的数据库天生对高并发写入/修改操作十分友好。不过LSM-TREE结构也不是完美的,在读性能上需要在多个副本之间做协同,因此读性能会受到一定的影响。
HEAP/BTREE结构的存储引擎虽然从结构上来说比较接近,不过依然存在一定的差异。在我们常见的数据库中,Oracle、PostgreSQL等是采用HEAP结构的,这种结构的页和不同的BTREE页不同,采用slotted page。Mysql、达梦等数据库使用的是传统的BTREE存储结构。
实际上这么说也不是很准确,只能说这些数据库的默认存储引擎是使用这种数据组织方式的。实际上Oracle中有heap表,有簇表,有混合列压缩的表。其中簇表是BREE结构的。达梦的存储引擎有多种数据组织方式:B树数据、堆表数据、列存数据、位图索引,其中B树数据是普通的达梦表的默认组织方式。
Oracle的数据块结构是一种典型的SLOTTED PAGE的结构,块头从上往下增长,而数据从尾部向块头生长。中间是一个可变长的slot指示器。
如上图所示,通过一个定长数组指示器结构,指出每一行行头的位置,这主要是为了解决不定长记录的存储问题,从而使空间利用率达到最高。
Oracle数据块使用一个kdbt的结构来指出某个block中有多少条记录,并且kdbpri这个指示器与块头的偏移量。kdbpri就是这个slot数组。
上面是MySQL innodb存储引擎的一个逻辑示意图,这是一种典型的BTREE结构存储引擎。BTREE结构的存储引擎也不是完全相同的。主要的区别是leaf node segment是否和数据存储在一个段里。达梦明显是分开的。实际上BTREE结构的存储结构,所有的数据存储都是按照主键的顺序存储的。
表空间是一个逻辑结构,可以被认为是innodb的顶层逻辑结构,所有的数据都必须属于某个表空间。默认的innodb引擎有一个ibdata1表空间,默认的数据都存储在这个表空格键中。如果设置了innodb_file_per_table参数,每张表都会创建独立的文件。不过只有表的数据、索引等会存储在每张表自己的文件中,UNDO数据、事务控制信息、INSERT BUFFER等仍然会存储在系统共享的表空间中。在Innodb存储引擎中,一张表会分为两个段,其中一个段是叶节点段,存储实际的数据,另外一个段是索引段,存储索引的指针信息。而表中DML的UNDO信息会存储在rollback segment中。
上面这张innodb的逻辑结构图画的十分清晰,所有的表的行数据是存储在extent中的,而每个extent是多个连续的PAGE组成的,每个PAGE中存储了行数据。实际上这个leaf node segment和Oracle的TABLE SEGMENT是十分类似的,所不同的是多了一个Index segments。相当于在创建表的时候同时又默认创建了一个主键索引。Mysql在窗这个主键索引的时候,会区分不同的情况。如果要创建的表上没有设置主键索引,那么会选择表上的一个非空唯一性索引作为主键索引,如果不存在这样的索引,那么Mysql会使用一个六字节的唯一性自增值窗一个主键索引。
Mysql innodb等采用B树存储结构的存储引擎一般采用上图的模式,当数据被插入表的时候,会根据主键索引或者簇索引的指示插入到某个位置,而不会像Oracle那样,通过segment的free space bitmap寻找空闲位置插入。
Innodb的PAGE结构与HEAP结构的类似,不过在空闲空间管理上是完全不同的。前面是FILE HEADER/PAGE HEADER,中间是数据记录,数据记录也是从低地址往高地址写,和Oracle相反。这是因为BTREE存储结构不需要和slotted page一样,在块里放一个指示器,其行指示器的功能被BTREE替代了。
Innodb的这种存储结构,并不存在一个十分友好的类似Oracle的记录物理地址的ROWID这样的结构。所以要想定位某条数据记录,需要使用主键或者簇主键的方式来实现。主键可以定义某条记录的唯一性地址,因此Mysql的某张表上的其他索引(secondary index)的索引中存储的键值不像Oracle那样存储ROWID就可以了,而是存储的是主键中这一行的地址指针。基于一个secondary index的查询首先找出某些行的主键,然后再去扫描一次主键索引,才能找到相关行的地址,再找到这条记录。比起有rowid的Oracle数据库,这里多了一次主键索引的扫描。
可能有些朋友会觉得,是不是heap结构一定优于BTREE结构呢?其实还是回到今天的标题,没有完美的存储引擎。针对不同的应用场景,heap和BTREE各有优势。BTREE结构写入数据时按主键排序的,而且并发写入时数据并不是按照插入顺序写入数据块,如果主键存在一定的无序性,那么并发写入的数据可以被打散到多个块中,从而缓解热块冲突的压力。而二级索引的结构虽然对读取数据的操作有影响,对于存在多条索引的数据写入,数据修改,是有优势的。因为只要主键的键值不变,行数据的变化,行在数据块中存储的变化,不需要变更第二索引。
因此我们可以十分明确的肯定,不同的存储结构都各有利弊,并不能很直接的说哪种更好。不过在开发高并发,大数据量的系统的时候,了解存储引擎的一些特点,可以有效的避免一些问题。比如在Mysql、达梦等数据库中建表,尽可能定义一个显式的主键,从而避免系统自动添加主键。另外如果某张表的热块冲突特别严重的时候,主键可以考虑选择随机性的数据,而不是单边增长的数据,就可以有效的进行数据打散,从而降低热块冲突的可能性。