作者:ChoyanaKwok楚楚 | 来源:互联网 | 2023-09-14 14:40
为实现不同的功能,GaussDB(DWS)提供了不同的数据对象类型,包括索引、行存表、列存表及其辅助表等。这些数据对象在特定的条件下实现不同的功能,为数据库的快速高效提供了保证,本文对部分数据对象进行介绍。
1. 索引(index)
索引是关系型数据库中对某一列或者多个列的值进行预排序的数据结构。如果数据库的记录非常多,通过建立索引可以获得非常快的查询速度,当对某一列建立索引之后,通过该列进行相关查询时数据库系统就不必扫描整个表,而是直接通过索引定位到符合条件的记录,在一定程度上能够大幅提升查询得速度。
假如需要执行如下的语句进行查询:
SELECT name FROM test_1 WHERE number =10;
一般情况下数据库需要对每一行进行遍历查询,直到找到所有满足条件 number=10 的元组信息。当数据库的记录很多,而满足 where 条件的记录又很少时,顺序扫描的性能就会很差。这时如果在表 test_1 的 number 属性上建立索引,用于快速定位需要匹配的元组信息,数据库只需要根据索引的数据结构进行搜索,由于常用的索引结构有 B-Tree、Hash、GiSt、GIN 等,这些索引结构的查询都是快速高效的,因此可以在少数几步内完成查询,大大提高了查询效率。
对表 test_1 的 number 属性建立索引语句如下:
CREATE INDEX numberIndex ON test_1(number);
由于 GaussDB 里的所有索引都是 “从属索引”,索引在物理文件上与原来的表文件分离,执行上述创建索引语句后,系统会生成 relname 为 numberIndex 的索引类型。表和索引都是数据库对象,在 pg_class 里会有该索引的记录,有与之相对应的 oid,同时在 pg_index 表里会记录索引及其对应主表的信息。对应属性信息如图 1 所示。
图 1 pg_index 部分属性
2.toast 表
toast(The Oversized-Atttibute Storage Techhnique)即超尺寸字段存储技巧,是数据库提供的一种存储大数据的机制。只有一些具有变长表现形式的数据类型才会支持 toast,比如 TEXT 类型。由于在 GaussDB (DWS) 的行存储方式中,一条数据的所有列组合在一起称之为一个 tuple,多个 tuple 组成一个 page。page 是数据在文件存储中的基本单位,其大小是固定的且只能在编译器指定,之后无法修改,默认发大小为 8KB,当某行数据很大超过 page 的大小时,数据库系统就会启动 toast,对数据进行压缩和切片。实际数据以行外存储的形式存储在另外一张表中,这张表就是 toast 表。
当一张表的任何一个属性是可以 toast 的,则这张表会有一张关联的 toast 表,在 pg_class 里表的 reltoastrelid 属性里记录了该 toast 表的 oid,如果没有关联的 toast 表,reltoastrelid=0。那么如何判断一张表的属性是否是可以 toast 的呢?我们可以在表的 Storage 选项中查看对应属性的存储策略。有以下四种不同的存储策略:
- PLAIN:避免压缩或者行外存储;此外,它禁止为变长类型使用单字节的头。 这只对那些不能 TOAST 的数据类型的列才有可能。
- EXTENDED:允许压缩和行外存储。 这是大多数 TOAST 数据类型的缺省策略。首先会尝试对数据进行压缩, 如果行仍然太大,则进行行外存储。
- EXTERNAL:允许行外存储,但是不许压缩。 使用 EXTERNAL,将使那些数据类型为 text 和 bytea 的字段上的子字符串操作更快 (代价是增加了存储空间),因为这些操作是经过优化的:如果行外数据没有压缩,那么它们只会获取需要的部分。
- MAIN:允许压缩,但不允许行外存储。 实际上,在这样的字段上仍然会进行行外存储, 但只是作为没有办法把数据行变得更小以使之足以放置在一个页面中的最后选择。
假如创建表语句如下:
CREATE TABLE test_t(id int,description text);
创建了一张 test_t 表,该表有 id 和 description 两个属性,分别属于 int 和 text 类型,查看该表的属性对应的 Storage 策略:
图 2 test_t 表相关信息
我们可以看出 description 属性的 Storage 策略为 EXTENDED,是可以 toast 的,系统会为 test_t 表创建一张关联的 toast 表。
图 3 test_t 表对应 toast 表
通过查询 pg_class,可以的看到表 test_t 关联的 toast 表的 oid 为 52579,进一步以此 oid 为条件在 pg_class 里就会得到 toast 表的相关信息。
图 4 toast 表相关信息
下图为 test_t 表和其对应的 toast 表之间的关系,以及 toast 表一些基本属性的介绍。
图 5 test_t 与其 toast 表关系图
3.cudesc 表
GaussDB (DWS) 除了提供行存储方式外,还支持列存储方式。列存储方式在数据压缩、列批量数据的运算、大数据统计分析等场景中有着显著的优势。CU(Compress Unit)压缩单元是列存储的最小单位,每列默认 60000 行存储在一个 CU 中,CU 生成后数据 固定不可更改。CUDesc 本身是一张行存表,它用来辅助记录列存表的 cu 信息,该表的每一行描述一个 CU,包括最大值最小值以及 CU 在文件中的偏移量和大小,连续多个行中各个不同的列的 cu_id 相同,可以认为就是把连续多个行截断拿出来,然后再根据不同的列,放到不同的 cu 中,这些 CU 所在的行数都是一致的,用一个 cu_id 表示,但是 col_id 不一样。同时还增加了一个 col_id=-10 的列,这个列为 VCU,表示这些连续的行中,有哪些行已经是被删除了,用 delete_map 记录删除信息。如图 6 所示。
图 6 cudesc 表示意图
每张列存表都有一张对应的 CUDesc 表,CUDesc 表的 oid 可以在 pg_class 中对应列存表元组的 relcudescrelid 属性中查到,所有 CUDesc 表默认存储在 namespace oid = 100,name 为 cstore 的 namespace 下。
4.delta 表
在列存储方式中,无论是向列存表中插入 1 条还是 60000 条数据,都只会生成一个 CU,在多次插入少量数据时,不能有效的利用列存压缩能力,导致数据膨胀影响查询的性能和磁盘使用率。CU 只支持追加写的方式,也就是说,后面对这个 CU 中的数据做更新或删除都不会真正更改这个 CU,删除是将老数据在字典中标记为作废,更新操作是标记老数据删除后,再写入一条新记录到新 CU,原来的 CU 不会有任何的修改。
从这里我们可以看出,在对列存表进行多次更新 / 删除,或每次只插入很少量的数据后,会导致列存表空间膨胀,大量空间无法有效利用,这是因为列存表在设计上就是为了大批量数据导入以及海量数据按列存储 / 查询。Delta 表正是为了解决这两个问题。在启用 delta 表后,单条或者小批量数据导入时,数据将进入 delta 表中,避免小 CU 的产生,delta 表的增删改查与行存表一致。开启 delta 表后,将显著提升列存表单条导入的性能。
delta 表同样是一张行存表,为了辅助列存表而存在。在创建列存表时系统会为该列存表创建一张对应的 delta 表,delta 表的 oid 可以在 pg_class 中对应列存表元组的 reldeltarelid 属性中查到,所有 delta 表也默认存储在 namespace oid = 100,name 为 cstore 的 namespace 下。
创建一张列存表 col_test,同时设置 reloption 属性 enable_delta=true。在 pg_class 中查看该表对应的 delta 表 oid。
图 7 创建列存表并开启 delta 表
进一步根据该 oid 信息可以查到 delta 表的对应信息。
图 8 查询 delta 表相关信息
可以指定 reloption 选项设置是否为该列存表开启 delta 表:
图 9 开启 / 关闭 delta 表操作
5. 分区表
分区表就是把逻辑上的一张表根据某种方案分成几张物理块进行存储。这张逻辑上的表称之为分区表,物理块称之为分区。分区表是一张逻辑表,不存储数据,数据实际是存储在分区上的。分区表的定义不难理解,下面我们通过一个例子说明分区表的用法。
创建一张有 id 和 name 两个属性的分区表 part_test,该表以 id 的大小进行分区,其中 id<10 的数据存储在分区 location_1,10≤id<20 的数据存储在分区 location_2,所有 id≥20 的数据存储在分区 location_3。
CREATE TABLE part_test(id int,name text) partition BY range(id) (partition locatition_1 values less than (10),partition locatition_2 values less than (20),partition locatition_3 values less than (maxvalue));
创建好 part_test 表后,我们所有的增删改查都是直接对 part_test 表操作的,对用户操作来说 part_test 表与普通表没有什么区别,但实际的存储方式却是严格按照分区的划分方式进行存储的,数据存储在各个分区上,part_test 表作为一张逻辑表不保存数据。我们可以通过 pg_partition 这张系统表查询到一张分区表的分区信息。
图 10 part_test 表分区信息
分区表和分区的关系如图所示:
图 11 分区表和分区关系图
6. 各类表相关对象总结