16.1 什么是列存储
达梦数据库中,表的数据存储方式分为行存储和列存储。行存储是以记录为单位进行存储的,数据页面中存储的是完整的若干条记录;列存储是以列为单位进行存储的,每一个列的所有行数据都存储在一起,而且一个指定的页面中存储的都是某一个列的连续数据。
下面以基表(表 16.1)为例,对行、列存储进行对比。
NAME 列 | ID 列 |
---|---|
SYSCLASSES | 268435457 |
SYSCOLUMNS | 268435458 |
SYSCONS | 268435459 |
SYSDUAL | 268435460 |
SYSGRANTS | 268435461 |
下图展示了相同的表数据在行存储表与列存储表中的不同存储方式。
Huge File System(简称 HFS)是达梦数据库实现的,针对海量数据进行分析的一种高效、简单的列存储机制。列存储表(也称为 HUGE 表)就是建立在 HFS 存储机制上的一种表。
16.2 什么是 HUGE 表
HUGE 表是建立在混合表空间上的。HUGE 数据文件存储在混合表空间定义中指定的 HUGE 数据文件路径下,一个混合表空间最多可以添加 127 个 HUGE 数据文件路径,混合表空间的 HUGE 数据文件路径相关信息存储在动态视图 V$HUGE_TABLESPACE 中。
普通的表空间,数据是通过段、簇、页来管理的,并且以固定大小(4K、8K、16K、32K)的页面为管理单位;而混合表空间存储 HUGE 表是通过 HFS 存储机制来管理的,它相当于一个文件系统。为混合表空间指定一个 HUGE 数据文件路径,其实就是创建一个空的 HUGE 数据文件目录(系统中有一个默认的混合表空间 MAIN,其 HUGE 数据文件目录名为 HMAIN)。在创建一个 HUGE 表并插入数据时,数据库会在指定的混合表空间的 HUGE 数据文件目录下创建一系列的目录及文件。HFS 文件系统结构如下图所示:
从上图可以看出,在 HUGE 数据文件目录下成功创建 HUGE 表,系统内部需要经过以下步骤:
首先,在 HUGE 数据文件目录下创建这个表对应的模式目录。目录名为“SCH+ 长度为 9 的 ID 号”组成的字符串,比如:SCH150995944。创建时如果这个目录已经存在,则无需重新创建。
其次,在模式目录下创建对应的表目录。表目录也是同样的道理,表目录名为“TAB+ 长度为 4 的 ID 号”组成的字符串,比如:TAB1112。表目录中存放的是这个表中所有的文件。
再次,在新创建表后插入数据时,每一个列对应一个以 dta 为后缀的文件,文件大小可以在建表时指定,默认为 64M,文件名为“COL+ 长度为 4 的列号 +_+ 长度为 10 的文件号”。例如,在图 16.2 中,列号 0000 表示第 1 列,列号 0001 表示第 2 列……;0000000000 表示第 1 个文件,0000000001 表示第 2 个文件……。最初一个列只有一个文件即可,当随着数据量不断增长,一个文件已放不下之后,系统会自动创建新的文件来存储不断增长的数据。
对于一个文件,其内部存储是按照区来管理的,区是文件内部数据管理的最小单位,也是唯一的单位。一个区中,可以存储单列数据的行数是在创建表时指定的,一经指定,在这个表的生命过程就不能再修改。所以,对于定长数据,一个区的大小是固定的;而对于变长数据,一般情况下区大小都是不相同的。每一个区的开始位置及长度在文件内都是 4K 对齐的。
HUGE 表的存储方式有以下几个优点:
- 同一个列的数据都是连续存储的,可以加快某一个列的数据查询速度;
- 连续存储的列数据,具有更大的压缩单元和数据相似性,可以获得远优于行存储的压缩效率,压缩的单位是区;
- 条件扫描借助数据区的统计信息进行精确过滤,可以进一步减少 IO,提高扫描效率;
- 允许建立二级索引;
- 支持以 ALTER TABLE 的方式添加或者删除 PK 和 UNIQUE 约束。
DM 支持两种类型的 HUGE 表:非事务型 HUGE 表和事务型 HUGE 表,下面分别进行介绍。
16.3 非事务型 HUGE 表
对非事务型 HUGE 表的增、删、改是直接对 HUGE 表进行写操作的,不写 UNDO 日志,不通过 BUFFER 缓存,直接操纵文件,速度快,但也因此导致不支持事务。另外,非事务型 HUGE 表中的 ROWID 是不固定的。
当非事务型 HUGE 表在操作过程中出现系统崩溃或者断电等问题时,因为修改时采取的是直接写的策略,所以有可能会出现数据不一致的问题。为了保证数据的一致性,在操作时可以适当地做一些日志来保证数据的完整性,完整性保证策略主要是通过数据的镜像来实现的,镜像的不同程度可以实现不同程度的完整性恢复。镜像文件是放在表目录中的以.mir 为扩展名的文件。DM 对于镜像提供三种方案:
- LOG NONE:不做镜像。相当于不做数据一致性的保证,如果出错只能手动通过系统函数来修复表数据,当然速度是最快的,不需要额外的 IO,如果用户明确知道自己的环境不会出现问题可以采用该方案,效率最高。
- LOG LAST:做部分镜像。但是在任何时候都只对当前操作的区做镜像,如果当前区的操作完成了,那么这个镜像也就失效了,可能会被下一个被操作区覆盖,这样做的好处是镜像文件不会太大,同时也可以保证数据是完整的。但有可能遇到的问题是:一次操作很多的情况下,有可能一部分数据做镜像已经完成,另一部分数据还没有来得及开始做的问题。如果用户能接受这个问题的话这个选择不失为最佳选择,这也是系统默认的选择。
- LOG ALL:全部做镜像。在操作过程中,所有被修改的区都会被记录下来,当一次操作修改的数据过多时,镜像文件有可能会很大,但好处是,能够保证操作完整性。比如,在操作过程中失败了,那么这个操作会完整的撤消,不存在 LOG LAST 方案中一部分修改部分还没修改的问题。
需要注意的是,若创建数据库时使用参数 HUGE_WITH_DELTA 的缺省值 1 ,则不支持创建非事务型 HUGE 表。
16.3.1 AUX 辅助表
对于每个 HUGE 表,相应地配备一个 AUX 辅助表来管理其数据。因为在 HUGE 数据文件(.dta 文件)中只存储了数据,辅助表用来管理以及辅助系统用户操作这些数据,AUX 辅助表是在创建 HUGE 表时系统自动创建的,表名为“表名$AUX”,如果该 HUGE 表为分区表,则辅助表名为“子表名$AUX”。辅助表的表名长度不能大于 128 个字节。AUX 辅助表中每一条记录对应文件中的一个数据区。
- COLID:表示当前这条记录对应的区所在的列的列 ID 号;
- SEC_ID:表示当前这个记录对应的区的区 ID 号,每一个区都有一个 ID 号,并且唯一;
- FILE_ID:表示这个区的数据所在的文件号;
- OFFSET:表示这个区的数据在文件中的偏移位置,4K 对齐;
- COUNT:表示这个区中存储的数据总数(有可能包括被删除的数据);
- ACOUNT:表示这个区中存储的实际数据行数;
- N_LEN:表示这个区中存储的数据在文件中的长度,4K 对齐的;
- N_NULL:表示这个区中的数据中包括的 NULL 值的行数;
- N_DIST:保留字段,无意义;
- CPR_FLAG:表示这个区是否压缩;
- ENC_FLAG:表示这个区是否加密;
- CHKSUM:用来存储标记位;
- MAX_VAL:表示这个区中的最大值,精确值;
- MIN_VAL:表示这个区中的最小值,精确值;
- SUM_VAL:表示这个区中所有值的和,精确值。
前面 7 列是用来控制数据存取的,根据这些信息就可以知道这个区的具体存储位置、长度及基本信息。后面 8 列都是用来对这个区进行统计分析的。其中,COLID 和 SEC_ID 的组合键为辅助表的聚集关键字。
16.4 事务型 HUGE 表
非事务型 HUGE 表在进行增、删、改时直接对 HUGE 表进行写操作,每次写操作需要至少对一个区进行 IO,导致 IO 量较大,且并发性能不高。
为此,DM 推出了事务型 HUGE 表,通过增加 RAUX、DAUX 和 UAUX 行辅助表,减少了事务型 HUGE 表增、删、改操作的 IO,提高效率,同时提高并行性能。事务型 HUGE 表支持 UNDO 日志,实现了事务特性。
16.4.1 RAUX 行辅助表
RAUX 行辅助表用于缓存向 HUGE 表插入的数据,表名为“HUGE 表名$RAUX”。如果该 HUGE 表为分区表,则辅助表名为“子表名$RAUX”。辅助表的表名长度不能大于 128 个字节。
RAUX 行辅助表中内容对应于 HUGE 表中的最后一部分记录(不够存满一个数据区的)。当 RAUX 表中数据达到区大小时,将 RAUX 表中数据以列为单位写入各列对应的文件中,并清空 RAUX 表。RAUX 表与 HUGE 表结构相同,不论数据在哪个表中,每一行数据的 ROWID 固定不变。
16.4.2 DAUX 行辅助表
DAUX 行辅助表记录 HUGE 数据文件中被删除的数据,表名为“HUGE 表名$DAUX”。如果该 HUGE 表为分区表,则辅助表名为“子表名$DAUX”。辅助表的表名长度不能大于 128 个字节。
DAUX 行辅助表中的一行数据对应 HUGE 表中一条或一段连续的被删除的数据,记录了被删除数据的起始行号和被删除数据的行数。删除 HUGE 表数据时,向 DAUX 行辅助表中插入对应删除数据的记录。
16.4.3 UAUX 行辅助表
UAUX 行辅助表记录 HUGE 表被更新的数据的新值,表名为“HUGE 表名$UAUX”。如果该 HUGE 表为分区表,则辅助表名为“子表名$UAUX”。辅助表的表名长度不能大于 128 个字节。
UAUX 行辅助表中一条记录对应 HUGE 表中更新列的一个值,记录了更新的列号、行号和更新后的值的二进制格式。更新 HUGE 表数据时,操作转换为对 UAUX 表的插入操作或更新操作。
16.5 创建 HUGE 表
当用户确定自己要使用 HUGE 表的时候,首先需要在模式中创建新表,创建一个 HUGE 表需要有 CREATE TABLE 数据库权限,要想在其他用户的模式中创建新表需要有 CREATE ANY TABLE 数据库权限。
但是创建一个 HUGE 表时,如果不使用默认的表空间,则必须要先创建一个混合表空间,创建混合表空间以及 HUGE 表的语法请参考《DM8_SQL 语言使用手册》。
在创建 HUGE 表时,根据 WITH|WITHOUT DELTA 区分创建非事务型 HUGE 表还是事务型 HUGE 表。指定 WITH DELTA,创建事务型 HUGE 表;指定 WITHOUT DELTA,则创建非事务型 HUGE 表,缺省为 WITH DELTA。若需要创建非事务型 HUGE 表,在创建数据库时需要将参数 HUGE_WITH_DELTA 和 RLOG_GEN_FOR_HUGE 置为 0。
例 1 创建非事务型 HUGE 表 T1
CREATE HUGE TABLE T1 (A INT, B INT) STORAGE(WITHOUT DELTA);
例 2 创建事务型 HUGE 表 T2
CREATE HUGE TABLE T2 (A INT, B INT) STORAGE(WITH DELTA);
需要注意的是,当指定创建事务型 HUGE 表时,指定 HUGE 表镜像文件方案的选项 LOG NONE|LOG LAST|LOG ALL 失效。
另外,在创建表 HUGE 表时,可以指定表的存储属性,存储属性包括如下几个方面:
- 区大小(一个区的数据行数)
区大小可以通过设置表的存储属性来指定,区的大小必须是 2 的 n 次方,如果不是则向上对齐。取值范围:1024 行~1024*1024 行。默认值为 65536 行。
例 1 创建 HUGE 表 test,指定区的大小为 2048,其它默认
CREATE HUGE TABLE test(name VARCHAR, sno INT) STORAGE(SECTION (2048));
例 2 创建 HUGE 表 test,所有列都采用默认值
CREATE HUGE TABLE test(name VARCHAR, sno INT);
- 是否记录区统计信息,即在插入时是否记下其最大值最小值
关于这一点有一个原则,如果这个列经常用作查询条件,并且数据不是很均匀,或者基本是有序的,那么做统计信息是非常有用的,反之则可以不做统计。缺省情况下,为记录区统计信息。如果想不记录,可通过设置 STAT NONE 实现。
例 1 创建 HUGE 表 test,通过列存储属性指定统计信息属性(不记录区统计信息)
CREATE HUGE TABLE test(name VARCHAR STORAGE (STAT NONE), sno INT);
例 2 创建 HUGE 表 test,通过表存储属性指定统计信息属性(不记录区统计信息)
CREATE HUGE TABLE test(name VARCHAR, sno INT) STORAGE (STAT NONE);
- 所属的表空间
创建 HUGE 表,需要通过存储属性指定其所在的表空间,不指定则存储于默认表空间 MAIN 中。HUGE 表指定的表空间只能是混合表空间,例如:TS1 为指定的混合表空间。
CREATE HUGE TABLE test(name VARCHAR, sno INT) STORAGE (ON TS1);
- 文件大小
创建 HUGE 表时还可以指定单个文件的大小,通过表的存储属性来指定,取值范围为 16M~1024*1024M。不指定则默认为 64M。文件大小必须是 2 的 n 次方,如果不是则向上对齐。
CREATE HUGE TABLE test(name VARCHAR, sno INT) STORAGE (filesize(64));
- 指定压缩级别
为特定列指定压缩级别,取值范围 0~10,分别代表不同的算法和级别。有两种压缩算法:SNAPPY 和 ZIP。10 采用 SNAPPY 算法轻量级方式压缩。2~9 采用 ZIP 算法压缩,2~9 代表压缩级别,值越小表示压缩比越低、压缩速率越快;值越大表示压缩比越高、压缩速度越慢。0 和 1 为快捷使用,默认值为 0。0 等价于 LEVEL 2;1 等价于 LEVEL 9。
例 创建 HUGE 表 test,指定 sno 列按照最大压缩比压缩
CREATE HUGE TABLE test(name VARCHAR, sno INT) COMPRESS LEVEL 1 (sno);
下面是一个综合的创建 HUGE 表的例子:
CREATE HUGE TABLE orders
(
o_orderkey INT,
o_custkey INT,
o_orderstatus CHAR(1),
o_totalprice FLOAT,
o_orderdate DATE,
o_orderpriority CHAR(15),
o_clerk CHAR(15),
o_shippriority INT,
o_comment VARCHAR(79) STORAGE(stat none)
)STORAGE(SECTION(65536) , FILESIZE(64), WITH DELTA, ON ts1) COMPRESS LEVEL 9 FOR 'QUERY HIGH' (o_comment);
这个例子创建了一个名为 orders 的事务型 HUGE 表,orders 表的区大小为 65536 行,文件大小为 64M,指定所在的混合表空间为 ts1,o_comment 列指定的区大小为不做统计信息,其它列(默认)都做统计信息,指定列 o_comment 列压缩类型为查询高压缩率,压缩级别为 9。
16.6 HUGE 表使用说明
HUGE 表与普通行表一样,可以进行增、删、改操作,操作方式也是一样的。使用 HUGE 表时应注意存在以下一些限制:
- 建 HUGE 表时仅支持定义 NULL、NOT NULL、UNIQUE 约束以及 PRIMARY KEY。后两种约束也可以通过 ALTER TABLE 的方式添加,但这两种约束不检查唯一性,用户需要确保实际数据符合约束,否则相关操作的结果可能不符合预期;
- HUGE 不允许建立聚集索引,允许建立二级索引,不支持建位图索引,其中 UNIQUE 索引不检查唯一性;
- 不支持 SPACE LIMIT(空间限制);
- 不支持建立全文索引;
- 不支持使用自定义类型;
- 不支持引用约束;
- 不支持 IDENTITY 自增列;
- 不支持大字段列;
- 不支持建触发器;
- 不支持游标的修改操作;
- 非事务型 HUGE 表 PRIMARY KEY 主键约束或 UNIQUE 唯一约束不检查唯一性,对应的索引都为虚索引。事务型 HUGE 表的 PRIMARY KEY 主键约束或 UNIQUE 唯一约束的唯一性检查由 DM.INI 参数 HUGE_UNIQUE_CHECK 决定,当 HUGE_UNIQUE_CHECK=1 时,PRIMARY KEY 主键约束或 UNIQUE 唯一约束对应的索引均为实索引;HUGE_UNIQUE_CHECK=0 时,创建索引规则仍然与非事务型 HUGE 表保持一致;
- 不允许对分区子表设置 SECTION 和 WITH/WITHOUT DELTA;
- 当事务型 HUGE 表进行了较多增删改操作时,应对其进行数据重整操作,以提高性能。
14.HUGE 表不支持删除 NOT NULL 约束,删除 PK 约束时,会保留 NOT NULL 约束;
15.DPC 环境下,HUGE 表加列时不支持以序列作为默认值。
16.7 查看有关 HUGE 表的信息
- 表定义
对一个 HUGE 表,用户可以通过 CALL SP_TABLEDEF('SYSDBA', 'ORDERS');得到这个表的定义语句,可以具体了解表的各个列的数据类型信息、存储属性等,还可以查看在这个表上是否有压缩等等。
- 数据存储情况
HUGE 表有一个很好的特点就是有 AUX 辅助表,其中用户可以利用的信息很多,因为每一条记录对应一个区,所以可以查看每一个区的存储情况,每一个列的存储情况及每一个列中具有相同区 ID 的所有数据的情况等,还包括了很精确的统计信息,用户可以通过观察 AUX 辅助表中的信息对表进行一些相应的操作。