建表最佳实践
分布键
云数据库 FusionDB 在创建表时必须显式指定分布键(DISTRIBUTED RANDOMLY 或 DISTRIBUTED BY (col1, col2,...))。默认情况下,如果创建表时不指定分布键,云数据库 FusionDB 将使用第一个字段作为分布键,这种情况有时会有比较大的风险所以应该坚决杜绝这种情况。
一般来说,均匀为第一大原则,选取更有业务意义的字段,建议选一个常用于Join的字段做分布键。分布键的作用是什么?一者,是为了把数据均匀的分散到所有Primary Instance;再者,是为了能够实现Local计算,尽量减少数据的shuffle。比如在做Join时,为了减少数据的重分布,只有当关联字段包含所有分布键字段的情况下,才能实现Local Join,才能保证数据减少发生shuffle。为了提升这种可能性,当然字段越少越好,只有单元素的集合才最容易成为其他集合的子集。
关于随机分布,我们需要有充分的认知,对于不太可能和其他大表进行关联的大表来说,如果我们实在找不到合适的分布键,有时我们可以使用Randomly策略,至少可以确保数据的均匀分布,当其与小表Join时,执行计划会优先选择将小表做Broadcast。分布键的选择是一个比较有技术含量的事情。对于Create时没有指定分布键又没有PK的情况,云数据库 FusionDB 将缺省使用第一个字段作为分布键,这种情况有时会有比较大的风险。
正确实例:
CREATE TABLE test_table ( createdate DATA, id INT, name varchar(64)) DISTRIBUTED RANDOMLY;
或
CREATE TABLE test_table ( createdate DATA, id INT, name varchar(64)) DISTRIBUTED RANDOMLY;
错误示例:
CREATE TABLE test_table ( createdate DATA, id INT, name varchar(64));
AOT
云数据库 FusionDB 中存储数据有2种存储模型:一种是默认类型的Heap表;另一种是appendonly表;
Heap表可以进行循环的Update、Delete和单行Insert操作,也可以进行并发的Update、Delete、Insert。
Appendonly表用于初始加载数据后很少更新,并且后续只是进行批量插入的操作。不推荐对Appendonly表进行单条Insert,Update,Delete操作和并发的Update,Delete操作,并发的Insert是可以进行的。由于对Appendonly进行Update和Delete的空间不会像Heap表那样被重复使用,因此Appendonly表不适合进行频繁的更新操作,Appendonly表适合于一次加载后频繁查询分析的大表。
Appendonly表相比于Heap表在数据存储时页面结构做了优化,每行记录相比于Heap表少20字节的行头信息,因此数据量更小,查询性能更好。因此可结合业务特点选择合适的。
Heap表实例:
CREATE TABLE test_table ( createdate DATA, id INT, name varchar(64)) DISTRIBUTED RANDOMLY;
AOT表实例:
CREATE TABLE test_table ( createdate DATA, id INT, name varchar(64))
WITH (APPENDONLY=true)<br>DISTRIBUTED RANDOMLY;
行存&列存
行存是数据库存储的传统方式,由多列组合而成的一行连续地存储在磁盘上,因此完整的一行使用单I/O即可从磁盘读取。
列存是每列都会创建一个文件,如果表带有分区,每个分区子表的每列都会创建一个文件。当一个Query仅仅访问带有很多列的列存表中的一小部分列时,其他不相关的列不会备从磁盘检索到,因此I/O的开销相比于行存会小很多。
行存适合于传统的频繁进行更新和插入的交易类型的事务,和对于常用每个Query都涉及记录中的很多列的情况。如果Select列表和Where条件需要数据表中大多数列,则此数据表采用行存最适合。此外,由于行存在灵活性和性能的综合方面有着很好的优势,因此行存适用于通用的或混合工作类型。
列存对于读取操作做了优化,但并未对写操作做优化,一行中的不同列会被写入到磁盘的不同区域。列存对于有很多列的宽表,并且Query只访问这些列中的一小部分集合的典型应用有非常好的性能。
另一方面,列存是把相同数据类型的值存储在一起,因此相比行存占用更少的空间,也使用更少的磁盘I/O。列存表也比行存表也有更好的压缩效果。
列存适合的场景:在数据仓库的分析工作中,对于经常需要对宽表小部分列做聚合计算的场景;有单个些列经常被更新并且不修改其他列的场景;
在一张宽表中读完整的一条记录时,列存相比行存需要花费更多的时间,因为每一列在每个segment上都是单独的一个文件。
最终选择行存还是列存,需要结合业务特征做最优的选择。
行存示例:
CREATE TABLE test_table ( createdate DATA, id INT, name VARCHAR(64))
WITH (APPENDONLY=true, ORIENTATION=row)
DISTRIBUTED RANDOMLY;
列存实例:
CREATE TABLE test_table ( createdate DATA, id INT, name VARCHAR(64))
WITH (APPENDONLY=true, ORIENTATION=column)
DISTRIBUTED RANDOMLY;
数据压缩
考虑到随着业务的发展,数据量存储的压力,对于数据量大一些的冷数据可以考虑采用压缩。只要压缩级别不是太高,都可以有效的降低存储空间,并且对查询性能不会造成太大的影响。常用的压缩选项是zlib5,但是如果提升到zlib9,可能压缩效果会提升10%+,但访问性能会成倍下降。有时候quicklz也是不错的选择,但其压缩效果不如zlib,当然访问性能也会比zlib好一些。具体生产场景可以结合业务场景做一些对比测试,如果访问是I/O瓶颈可以采用压缩存储,使用CPU来换I/O,并可以测试不同的压缩算法和级别以获得最满意的压缩选项。列压缩的压缩率比行压缩的更好,但应与表尺寸一并考虑。
对于大表来说,采用压缩存储,既节省空间也节省IO资源。长远来看还可降低阵列卡和磁盘的故障率。 只有AOT表才能使用使用表压缩,Heap不能使用压缩。
压缩示例(使用Zlib方式,压缩级别为5)
CREATE TABLE test_table ( createdate DATA, id INT, name VARCHAR(64))
WITH (APPENDONLY=true, ORIENTATION=row, COMPRESSTYPE=zlib, COMPRESSLEVEL=5)
DISTRIBUTED RANDOMLY;
分区表
云数据库 FusionDB 的分区原理与其他数据库无异,合适的分区可以减少磁盘I/O操作加速查询效率,但是建议是不要把表分区设计的过于零碎,一般来说一个表的分区数量,几十、上百个就差不多了,不宜过多,对应冷数据可以做分区合并并压缩存储已节省存储空间。分区之间数据无需均匀。具体的分区策略是否合理,需要根据具体情况来评估。
分区表最好选择日期字段做为分区键,按月或者按天分区,具体可以根据业务的使用而决定。
另外,一般不建议用多级分区,多级分区维护困难,同时数据文件的数量也会剧增。
按月分区示例:
CREATE TABLE test_table ( createdate DATA, id INT, name VARCHAR(64))
WITH (APPENDONLY=true, ORIENTATION=row, COMPRESSTYPE=zlib, COMPRESSLEVEL=5)
DISTRIBUTED RANDOMLY
PARTITION BY range(createdate)
(
PARTITION m201801 START (date '2018-01-01') INCLUSIVE END (date '2018-02-01') EXCLUSIVE,
PARTITION m201802 START (date '2018-02-01') INCLUSIVE END (date '2018-03-01') EXCLUSIVE,
PARTITION m201803 START (date '2018-03-01') INCLUSIVE END (date '2018-04-01') EXCLUSIVE,
PARTITION m201804 START (date '2018-04-01') INCLUSIVE END (date '2018-05-01') EXCLUSIVE,
PARTITION m201805 START (date '2018-05-01') INCLUSIVE END (date '2018-06-01') EXCLUSIVE,
PARTITION m201806 START (date '2018-06-01') INCLUSIVE END (date '2018-07-01') EXCLUSIVE,
PARTITION m201807 START (date '2018-07-01') INCLUSIVE END (date '2018-08-01') EXCLUSIVE,
PARTITION m201808 START (date '2018-08-01') INCLUSIVE END (date '2018-09-01') EXCLUSIVE,
PARTITION m201809 START (date '2018-09-01') INCLUSIVE END (date '2018-10-01') EXCLUSIVE,
PARTITION m201810 START (date '2018-10-01') INCLUSIVE END (date '2018-11-01') EXCLUSIVE,
PARTITION m201811 START (date '2018-11-01') INCLUSIVE END (date '2018-12-01') EXCLUSIVE,
PARTITION m201812 START (date '2018-12-01') INCLUSIVE END (date '2019-01-01') EXCLUSIVE,
DEFAULT PARTITION other
);
数据类型
在建表时,各字段的数据类型的选择也是比较讲究的:
- 使用相同数据类型的字段进行表Join操作,如果数据类型不一致,那么云数据库 FusionDB 需要对字段进行动态类型转化以确保字段可以比较,另外,由于字段之间类型不匹配,很可能会导致系统选用错误的Join方式,从而查询性能严重下降。因此在建表时需兼顾各表中字段类型的兼容。
- 选择正确的数据类型。比如一个日期字段,可以设置为date、varchar等类型,但除非是特殊要求,否则最为合适的是选用date类型。因为在云数据库 FusionDB 中,如果使用日期字段作为过滤条件,date类型比较效率比varchar类型的比较效率要高;再者无法针对varchar类型对表进行基于时间的分区,这对大表的优化造成影响。