良好的表结构设计不仅能支持丰富的功能需求,还能大幅提升数据库系统的性能、可维护性和可扩展性等,因此数据库的表结构设计至关重要。本文为您介绍在云数据库 SelectDB 版中,设计表结构时需要重点关注的表属性,并帮助您快速掌握如何根据业务场景选择合适的表设计,从而更好的满足业务需求。
重要表属性概览
在业务接入SelectDB时,根据业务场景做好重要表属性的设计,对于构建出能满足业务需求且高性能、易维护的表结构至关重要。以下是SelectDB重要表属性的快速概览。
表属性 | 是否必选 | 属性关键作用 | 详情链接 |
数据模型 | 是 | 不同的数据模型适用于不同的业务场景:Unique模型支持主键唯一性约束,可满足灵活高效的数据更新需求。 Duplicate模型采用追加写模式,适用于明细数据的高性能分析场景。 Aggregate模型支持数据预聚合,专注于数据聚合统计场景。 | |
分桶 | 是 | 分桶用于将数据分散到集群中的不同节点,以充分利用分布式系统的分而治之的能力来管理和查询海量数据。 | |
分区 | 否 | 分区能够根据指定字段(如时间、地域等)将原始表划分为多个子表,以便于对数据进行分区管理和查询,同时利用分区裁剪来提升查询速度。 | |
索引 | 否 | 索引能够快速地过滤或定位数据,从而大幅提升查询性能。 |
数据模型
数据模型的合理选择,对于能否满足数据分析场景的功能需求和性能要求具有决定性影响。不同的模型适用于不同的业务场景。此处仅对各个模型进行简要介绍,旨在帮助您快速了解数据模型,以便于您进行模型选择。更多详情,请参见专题介绍文章数据模型。
基础概念
在SelectDB中,数据通过表(Table)的形式在逻辑层面进行组织和管理。每张表由行(Row)和列(Column)组成。行表示数据表中的一行数据,而列用于描述该行数据中的不同字段。
列可以分为以下两大类:
Key列:Key列是指建表语句中被关键字
UNIQUE KEY
、AGGREGATE KEY
或DUPLICATE KEY
修饰的列。Value列:除Key列外,其余列均为Value列。
模型选择指导
在SelectDB中,表的数据模型分为三种,分别为Unique模型、Duplicate模型和Aggregate模型。
数据模型在建表时已确定且不可修改。
如果在建表时未指定数据模型,将默认采用Duplicate模型,并自动选择前三列作为Key列。
Unique模型、Duplicate模型和Aggregate模型中,数据均按照Key列进行排序存储。
模型类型 | 模型特点 | 适用场景 | 模型不足 |
Unique | 每一行的Key值唯一。 Key列值相同时,多行数据的Value列会按写入的先后顺序进行覆盖。 | 适用于对数据有唯一主键要求或高效更新要求的场景。例如电商订单、用户属性信息等数据分析场景。 |
|
Duplicate | 允许多行的Key值相同。 Key列值相同时,多行数据同时存储在系统中。 | 数据的写入和查询效率极高,适用于保留所有原始数据记录的场景。例如日志、账单等明细数据分析场景。 |
|
Aggregate | 每一行的Key值唯一。 Key列值相同时,多行数据的Value列会按照建表时指定的聚合方式进行预聚合。 | 类似于传统数据仓库的Cube模型,适用于通过预聚合提升查询性能的聚合统计场景。例如网站流量分析、定制化报表等数据分析场景。 |
|
快速使用模型
Unique模型
在Unique模型中,Key列值相同时,多行数据的Value列会按写入的先后顺序进行覆盖。Unique模型提供了两种实现方式:读时合并(MOR,merge-on-read)和写时合并(MOW,merge-on-write)。
由于写时合并技术已非常成熟且稳定,并且能够提供优异的查询性能,因此建议您优先采用写时合并的实现方式。此处仅简要介绍Unique模型的写时合并。有关读时合并的详细信息,请参见读时合并(MOR)。
注意事项
创建Unique模型且为写时合并实现方式的表时,须在建表时注意以下事项。
通过
UNIQUE KEY
指定主键唯一的字段。在PROPERTIES中添加开启写时合并的属性。
"enable_unique_key_merge_on_write" = "true"
示例
创建orders表语句如下。其表示将orders表指定为Unique模型,同时将orders表主键设定为由order_id和order_time组成的联合主键,并启用写时合并模式。
CREATE TABLE IF NOT EXISTS orders
(
`order_id` LARGEINT NOT NULL COMMENT "订单id",
`order_time` DATETIME NOT NULL COMMENT "订单时间",
`customer_id` LARGEINT NOT NULL COMMENT "用户id",
`total_amount` DOUBLE COMMENT "订单总金额",
`status` VARCHAR(20) COMMENT "订单状态",
`payment_method` VARCHAR(20) COMMENT "支付方式",
`shipping_method` VARCHAR(20) COMMENT "运输方式",
`customer_city` VARCHAR(20) COMMENT "用户所在城市",
`customer_address` VARCHAR(500) COMMENT "用户地址"
)
UNIQUE KEY(`order_id`, `order_time`)
PARTITION BY RANGE(`order_time`) ()
DISTRIBUTED BY HASH(`order_id`)
PROPERTIES (
"enable_unique_key_merge_on_write" = "true",
"dynamic_partition.enable" = "true",
"dynamic_partition.time_unit" = "DAY",
"dynamic_partition.start" = "-7",
"dynamic_partition.end" = "3",
"dynamic_partition.prefix" = "p",
"dynamic_partition.create_history_partition" = "true",
"dynamic_partition.buckets" = "16"
);
Duplicate模型
在Duplicate模型中,Key列值相同时,多行数据同时存储在系统中,没有预聚合、主键唯一性约束等特点。
例如,您希望记录并分析业务系统产生的日志数据,且期望数据按照日志时间、日志类型、错误码进行排序存储,您可以选择此模型。具体创建log表的语句如下,其表示log表模型为Duplicate模型,且数据会按照log_time、log_type和error_code进行排序。
CREATE TABLE IF NOT EXISTS log
(
`log_time` DATETIME NOT NULL COMMENT "日志时间",
`log_type` INT NOT NULL COMMENT "日志类型",
`error_code` INT COMMENT "错误码",
`error_msg` VARCHAR(1024) COMMENT "错误详细信息",
`op_id` BIGINT COMMENT "负责人id",
`op_time` DATETIME COMMENT "处理时间"
)
DUPLICATE KEY(`log_time`, `log_type`, `error_code`)
PARTITION BY RANGE(`log_time`) ()
DISTRIBUTED BY HASH(`log_type`)
PROPERTIES (
"dynamic_partition.enable" = "true",
"dynamic_partition.time_unit" = "DAY",
"dynamic_partition.start" = "-7",
"dynamic_partition.end" = "3",
"dynamic_partition.prefix" = "p",
"dynamic_partition.create_history_partition" = "true",
"dynamic_partition.buckets" = "16"
);
Aggregate模型
注意事项
在Aggregate模型中,Key列值相同时,多行数据的Value列会按照建表时指定的聚合方式进行预聚合。因此,在创建Aggregate模型的表时,需特别关注以下事项。
通过
AGGREGATE KEY
指定Key列,相同Key列的数据行将进行聚合。指定Value的聚合方式。目前支持的聚合方式如下:
聚合方式参数
参数说明
SUM
求和。适用数值类型。
MIN
求最小值。适合数值类型。
MAX
求最大值。适合数值类型。
REPLACE
替换。对于维度列相同的行,指标列会按照导入的先后顺序,后导入的替换先导入的。
REPLACE_IF_NOT_NULL
非空值替换。和REPLACE的区别在于对于null值,不做替换。这里要注意的是字段默认值要给NULL,而不能是空字符串,如果是空字符串,会给你替换成空字符串。
HLL_UNION
HLL类型的列的聚合方式,通过HyperLogLog算法聚合。
BITMAP_UNION
BITMAP类型的列的聚合方式,进行位图的并集聚合。
示例
例如,您需要对用户行为进行统计分析,记录其最后访问时间、总消费额、最大停留时间和最短停留时间,您可以选择此模型。具体创建user_behavior表的语句如下。其表示当多条数据的Key列(用户ID、数据写入日期、用户所在城市、用户年龄和用户性别)相同时,用户的Value列进行预聚合。聚合规则如下:
用户最后一次访问时间:取多条用户行为数据中last_visit_date字段的最大值。
用户总消费:多条数据中用户消费的总和。
用户最大停留时间:取多条用户行为数据中max_dwell_time字段的最大值。
用户最小停留时间:取多条用户行为数据中min_dwell_time字段的最小值。
CREATE TABLE IF NOT EXISTS user_behavior
(
`user_id` LARGEINT NOT NULL COMMENT "用户id",
`date` DATE NOT NULL COMMENT "数据写入日期时间",
`city` VARCHAR(20) COMMENT "用户所在城市",
`age` SMALLINT COMMENT "用户年龄",
`sex` TINYINT COMMENT "用户性别",
`last_visit_date` DATETIME REPLACE DEFAULT "1970-01-01 00:00:00" COMMENT "用户最后一次访问时间",
`cost` BIGINT SUM DEFAULT "0" COMMENT "用户总消费",
`max_dwell_time` INT MAX DEFAULT "0" COMMENT "用户最大停留时间",
`min_dwell_time` INT MIN DEFAULT "99999" COMMENT "用户最小停留时间"
)
AGGREGATE KEY(`user_id`, `date`, `city`, `age`, `sex`)
PARTITION BY RANGE(`date`) ()
DISTRIBUTED BY HASH(`user_id`)
PROPERTIES (
"dynamic_partition.enable" = "true",
"dynamic_partition.time_unit" = "DAY",
"dynamic_partition.start" = "-7",
"dynamic_partition.end" = "3",
"dynamic_partition.prefix" = "p",
"dynamic_partition.create_history_partition" = "true",
"dynamic_partition.buckets" = "16"
);
数据划分概述
SelectDB支持两层数据划分,如下图所示。第一层是分区(Partition),用于对数据进行逻辑划分,分区是用户进行数据管理的最小单元。第二层是分桶(Tablet),用于对数据进行物理划分,分桶是系统进行数据打散、移动等操作的最小单元。
分区与分桶的关联
一个分桶仅属于一个分区,而一个分区则包含多个分桶。
在建表过程中,如果采用了分区(Partition),则表先按分区规则进行划分,分区内再按指定的分桶规则进行划分;如果未使用分区,则表直接按指定的分桶规则进行划分。
在数据写入过程中,数据首先被划分至相应的分区,随后在分区内依据分桶规则进一步写入至不同的分桶中。分桶是对分区数据的进一步细分,其目的是为了更加均匀地分布数据,从而提升查询效率。
分区(Partition)
在SelectDB的存储引擎中,分区是一种数据组织方式,用于将表中的数据按照用户定义的规则划分为多个独立的部分,从而实现数据的逻辑划分。这有助于提升查询效率,同时也使得数据管理更加灵活和便捷。此处仅对分区进行简要介绍,旨在帮助您快速了解分区,以便于您进行分区选型。更多详情,请参见分区和动态分区。
分区选择指导
SelectDB支持两种分区方式:Range分区和List分区。同时,也提供了简单易用的动态分区功能,以实现对分区的自动化管理。不同的分区方式,适用于不同的业务场景。
分区方式 | 支持的列类型 | 指定分区信息方式 | 适用场景 |
Range | 列类型:DATE、DATETIME、TINYINT、SMALLINT、INT、BIGINT、LARGEINT | 支持四种写法:
| 适用于对数据划分区间进行管理,典型的场景是按时间进行分区。 |
List | 列类型:BOOLEAN、TINYINT、SMALLINT、INT、BIGINT、LARGEINT、DATE、DATETIME、CHAR、VARCHAR | 支持通过 | 适用于依据数据的既有类别或固定特性进行数据管理,分区列通常为枚举值,例如根据用户所属地域进行数据划分管理。 |
注意事项
SelectDB的表可分为分区表和无分区表。该属性可选,在建表时确定是否进行分区,之后不可更改。具体而言,对于分区表,您可以在后续使用过程中对分区进行增删操作;而对于无分区表,则无法再进行增加分区等操作。
分区列必须为Key列,可指定一列或多列。
不论分区列是什么类型,在写分区值时,都需要加双引号。
分区的数量在理论上并没有上限。
在创建分区时,必须确保每个分区的取值范围不重叠。
快速使用分区
Range分区
Range分区是指按照分区字段范围,对数据进行划分管理,是最常用的分区方式。典型的使用场景是按照时间进行数据分区,方便对海量的时间序数据进行管理、查询优化等。
分区分桶的最终目的是合理地划分数据,分区规则设置合理性的主要标准是:
在分区及分桶规则下,每个分桶(Tablet)的数据量在1~10 GB的范围内;
根据您管理数据的粒度确定分区粒度(例如,在日志场景下,您通常需要按天淘汰历史数据,此时选择以天为分区粒度比较合适)。
在日志场景下,经常按照时间范围过滤查询数据,且需要按照时间淘汰历史分区,您可以将log_time
字段指定为分区列,并采取按天分区的方式,示例如下。
CREATE TABLE IF NOT EXISTS log
(
`log_time` DATETIME NOT NULL COMMENT "日志时间",
`log_type` INT NOT NULL COMMENT "日志类型",
`error_code` INT COMMENT "错误码",
`error_msg` VARCHAR(1024) COMMENT "错误详细信息",
`op_id` BIGINT COMMENT "负责人id",
`op_time` DATETIME COMMENT "处理时间"
)
DUPLICATE KEY(`log_time`, `log_type`, `error_code`)
PARTITION BY RANGE(`log_time`)
(
PARTITION `p20240201` VALUES [("2024-02-01"), ("2024-02-02")),
PARTITION `p20240202` VALUES [("2024-02-02"), ("2024-02-03")),
PARTITION `p20240203` VALUES [("2024-02-03"), ("2024-02-04"))
)
DISTRIBUTED BY HASH(`log_type`)
PROPERTIES ();
建表完成后,您可以通过如下SQL查看表的分区信息。
SHOW partitions FROM log;
p20240201: [("2024-02-01"), ("2024-02-02"))
p20240202: [("2024-02-02"), ("2024-02-03"))
p20240203: [("2024-02-03"), ("2024-02-04"))
当您使用以下语句查询数据时,就会命中分区p20240202: [("2024-02-02"), ("2024-02-03"))
,系统不会扫描剩余两个分区的数据,从而提高了查询数据的速度。
SELECT * FROM orders WHERE order_time = '2024-02-02';
List分区
List分区是按照分区字段的枚举值,对数据进行划分管理。当对采用List分区的表进行查询时,可结合过滤条件快速进行分区裁剪,提升查询性能。
您可以根据操作业务数据时常用的字段来选择List分区列。需要注意的是,各个分区之间的数据量要均匀,避免严重的数据倾斜。
例如,在电商场景中,订单数据量通常非常庞大,且某些场景经常需要根据订单用户所属城市来查询分析此类数据。因此,为了更方便地管理和查询数据,可以将customer_city
字段指定为分区列。假如数据量按地域的分布如下:
北京、上海和中国香港预计有6GB。
纽约、旧金山预计有5GB。
东京预计有5GB。
此时您可以按照以下示例进行分区。
CREATE TABLE IF NOT EXISTS orders
(
`order_id` LARGEINT NOT NULL COMMENT "订单id",
`order_time` DATETIME NOT NULL COMMENT "订单时间",
`customer_city` VARCHAR(20) COMMENT "用户所在城市",
`customer_id` LARGEINT NOT NULL COMMENT "用户id",
`total_amount` DOUBLE COMMENT "订单总金额",
`status` VARCHAR(20) COMMENT "订单状态",
`payment_method` VARCHAR(20) COMMENT "支付方式",
`shipping_method` VARCHAR(20) COMMENT "运输方式",
`customer_address` VARCHAR(500) COMMENT "用户地址"
)
UNIQUE KEY(`order_id`, `order_time`, `customer_city`)
PARTITION BY LIST(`customer_city`)
(
PARTITION `p_cn` VALUES IN ("Beijing", "Shanghai", "Hong Kong"),
PARTITION `p_usa` VALUES IN ("New York", "San Francisco"),
PARTITION `p_jp` VALUES IN ("Tokyo")
)
DISTRIBUTED BY HASH(`order_id`) BUCKETS 16
PROPERTIES (
"enable_unique_key_merge_on_write" = "true"
);
建表完成后,您可以通过如下SQL查看表的分区信息,该表会自动生成以下3个分区。
SHOW partitions FROM orders;
p_cn: ("Beijing", "Shanghai", "Hong Kong")
p_usa: ("New York", "San Francisco")
p_jp: ("Tokyo")
当您使用以下语句查询数据时,就会命中分区p_jp: ("Tokyo")
,系统不会扫描剩余两个分区的数据,从而提高了查询数据的速度。
SELECT * FROM orders WHERE customer_city = 'Tokyo';
使用动态分区
实际生产环境中,数据表的分区数量可能比较多,此时手动管理分区的工作将变得十分繁琐,这为数据库管理人员带来了额外的维护成本。SelectDB允许在建表时设定动态分区规则以进行自动化分区管理。
例如,在电商中,针对订单信息表,经常按照时间范围过滤查询数据,且需要对历史订单进行转储归档的场景。您可以将order_time
字段指定为分区列,并在PROPERTIES中设置动态分区属性。比如在PROPERTIES中设置该分区采取按天分区(dynamic_partition.time_unit)的方式,只保留最近180天(dynamic_partition.start)的分区,并且预先创建未来3天(dynamic_partition.end)的分区,示例如下。
下述语句中PARTITION BY RANGE(`order_time`) ()
末尾的()
并非语法错误,如果您要使用动态分区,此括号为固定语法,必不可少。
CREATE TABLE IF NOT EXISTS orders
(
`order_id` LARGEINT NOT NULL COMMENT "订单id",
`order_time` DATETIME NOT NULL COMMENT "订单时间",
`customer_id` LARGEINT NOT NULL COMMENT "用户id",
`total_amount` DOUBLE COMMENT "订单总金额",
`status` VARCHAR(20) COMMENT "订单状态",
`payment_method` VARCHAR(20) COMMENT "支付方式",
`shipping_method` VARCHAR(20) COMMENT "运输方式",
`customer_city` VARCHAR(20) COMMENT "用户所在城市",
`customer_address` VARCHAR(500) COMMENT "用户地址"
)
UNIQUE KEY(`order_id`, `order_time`)
PARTITION BY RANGE(`order_time`) ()
DISTRIBUTED BY HASH(`order_id`)
PROPERTIES (
"enable_unique_key_merge_on_write" = "true",
"dynamic_partition.enable" = "true",
"dynamic_partition.time_unit" = "DAY",
"dynamic_partition.start" = "-180",
"dynamic_partition.end" = "3",
"dynamic_partition.prefix" = "p",
"dynamic_partition.create_history_partition" = "true",
"dynamic_partition.buckets" = "16"
);
当您预计表的分区数量较多时,强烈建议您学习动态分区的相关内容,详情请参见动态分区。
分桶(Tablet)
在SelectDB的存储引擎中,数据根据指定列的Hash值划分到不同的分桶(Tablet),分桶则由集群中的不同节点进行管理,从而利用分布式系统的能力来进行管理、查询海量数据。在建表时,通过DISTRIBUTED BY HASH(`<分桶列>`) BUCKETS <分桶数量>
进行分桶的设置。有关分桶的详细信息,请参见分桶。
注意事项
如果建表时使用了分区(Partition),则
DISTRIBUTED...
语句所描述的是数据在各个分区内的划分规则;如果未使用分区,则所描述的是对整个表的数据划分规则。分桶列可以包含多个。
对于Aggregate和Unique模型,分桶列必须为Key列;而对于Duplicate模型,分桶列则没有限制。
分桶列建议选择唯一值多的高基数列,以便打散数据,避免数据倾斜。
分桶(Tablet)的数量理论上没有上限。
单个分桶(Tablet)的数据量理论上没有上下界,但建议在1~10 GB的范围内。
如果单个分桶(Tablet)数据量过小,容易导致分桶过多,元数据管理压力增大。
如果单个分桶(Tablet)数据量过大,不利于副本的迁移、分布式集群的充分利用,增加Schema变更或者索引创建等操作失败重试的代价(这些操作失败重试的粒度是Tablet)。
分桶列选择指导
在表设计过程中,分桶列的选择对查询的性能和并发量有着重要影响,分桶列选择的原则如下所示。当业务中存在多种查询需求时,会产生多种分桶列的期望,此时应优先根据最主要查询的需求进行选择。
选择原则 | 作用 |
优先保障数据均匀打散,选择高基数列或多列组合。 | 数据在集群节点上分布更均衡。对于过滤效果不佳、进行大量数据扫描的查询,可充分利用分布式系统的资源提升查询性能。 |
选择经常用于查询过滤条件的列,兼顾数据裁剪加速查询。 | 相同分桶列的数据聚集在一起。对于指定分桶列作为过滤条件的点查询,可快速进行数据裁剪提高查询并发。 说明 点查询通常用于从数据库中检索特定条件下的少量数据。这种查询通过指定特定的条件(例如通过主键、高基数列进行过滤),来精确确定位并获取数据库中符合条件的少量数据。 |
使用示例
在电商场景中,大量查询按照订单维度进行过滤查询,也有部分查询对全量订单数据进行统计分析。此时,您可以选择订单信息表Key列中的高基数列order_id作为分桶列,可保障数据能够均匀地分配到每个桶,且单个order_id的数据聚集在一起,可同时满足前述两种类型查询的性能需求。具体建表语句如下。
CREATE TABLE IF NOT EXISTS orders
(
`order_id` LARGEINT NOT NULL COMMENT "订单id",
`order_time` DATETIME NOT NULL COMMENT "订单时间",
`customer_id` LARGEINT NOT NULL COMMENT "用户id",
`total_amount` DOUBLE COMMENT "订单总金额",
`status` VARCHAR(20) COMMENT "订单状态",
`payment_method` VARCHAR(20) COMMENT "支付方式",
`shipping_method` VARCHAR(20) COMMENT "运输方式",
`customer_city` VARCHAR(20) COMMENT "用户所在城市",
`customer_address` VARCHAR(500) COMMENT "用户地址"
)
UNIQUE KEY(`order_id`, `order_time`)
PARTITION BY RANGE(`order_time`) ()
DISTRIBUTED BY HASH(`order_id`)
PROPERTIES (
"enable_unique_key_merge_on_write" = "true",
"dynamic_partition.enable" = "true",
"dynamic_partition.time_unit" = "DAY",
"dynamic_partition.start" = "-7",
"dynamic_partition.end" = "3",
"dynamic_partition.prefix" = "p",
"dynamic_partition.create_history_partition" = "true",
"dynamic_partition.buckets" = "16"
);
索引
索引在数据库设计中至关重要,合适的索引可大幅提高查询性能。创建索引存在一定的成本,可能导致额外的存储空间占用以及写入性能的下降。此处仅对常用索引进行简要介绍,以帮助您快速了解索引,以便于您进行索引选型。更多详情,请参见索引加速。
设计指南
最常使用的过滤条件应指定为Key自动建立前缀索引,因为其过滤效果最佳。然而,一个表只能拥有一个前缀索引,因此建议将其应用于最频繁的过滤条件上。
对于其他过滤加速需求,首选创建倒排索引,因其适用范围广泛,支持多条件组合。对于字符串的等值、LIKE匹配查询场景,可考虑轻量级的BloomFilter、NGram BloomFilter索引。
索引选择指导
在SelectDB中,表的索引的构建方式有内建和自定义两种。对于内建索引,系统会自动创建。而对于自定义索引,需要您在建表时或者建表后,根据需要自行创建。
构建方式 | 索引类型 | 支持的查询类型 | 不支持的查询类型 | 优势 | 劣势 |
内建 | 前缀索引 |
|
| 前缀索引占用的空间相对较小,能够在内存中进行全量缓存,从而实现快速定位数据块,显著提升查询效率。 | 一个表只能有一个前缀索引。 |
自定义 | 倒排索引(推荐) |
| 无 | 支持的查询类型丰富。支持在建表时、建表后按需创建索引,并支持索引删除。 | 索引存储空间相对较大。 |
BloomFilter索引 | 等于查询 |
| 索引构建占用的计算和存储资源少。 | 支持的查询类型少,只支持等于查询。 | |
NGram BloomFilter索引 | LIKE查询 |
| 提高LIKE查询速度,索引构建占用的计算和存储资源少。 | 只支持LIKE加速。 |
快速使用索引
倒排索引
SelectDB支持倒排索引,可用于满足文本字段的全文检索、以及普通字段的等值或范围查询,能够快速从大量数据中筛选出满足条件的数据。此处仅简绍如何创建倒排索引,更多信息,请参见倒排索引。
建表时创建索引
在电商场景中,根据用户ID、用户地址关键词查询订单信息是高频操作,此时可以在customer_id
和customer_address
字段上建立倒排索引来提高查询速度。具体建表语句如下。
CREATE TABLE IF NOT EXISTS orders
(
`order_id` LARGEINT NOT NULL COMMENT "订单id",
`order_time` DATETIME NOT NULL COMMENT "订单时间",
`customer_id` LARGEINT NOT NULL COMMENT "用户id",
`total_amount` DOUBLE COMMENT "订单总金额",
`status` VARCHAR(20) COMMENT "订单状态",
`payment_method` VARCHAR(20) COMMENT "支付方式",
`shipping_method` VARCHAR(20) COMMENT "运输方式",
`customer_city` VARCHAR(20) COMMENT "用户所在城市",
`customer_address` VARCHAR(500) COMMENT "用户地址",
INDEX idx_customer_id (`customer_id`) USING INVERTED,
INDEX idx_customer_address (`customer_address`) USING INVERTED PROPERTIES("parser" = "chinese")
)
UNIQUE KEY(`order_id`, `order_time`)
PARTITION BY RANGE(`order_time`) ()
DISTRIBUTED BY HASH(`order_id`)
PROPERTIES (
"enable_unique_key_merge_on_write" = "true",
"dynamic_partition.enable" = "true",
"dynamic_partition.time_unit" = "DAY",
"dynamic_partition.start" = "-7",
"dynamic_partition.end" = "3",
"dynamic_partition.prefix" = "p",
"dynamic_partition.create_history_partition" = "true",
"dynamic_partition.buckets" = "16"
);
已有表创建索引
在电商场景中,根据用户ID查询订单信息数据是一个高频操作,但如果建表时没有为customer_id
字段创建倒排索引,可以使用以下语句为其增加索引。
ALTER TABLE orders ADD INDEX idx_customer_id (`customer_id`) USING INVERTED;
前缀索引
前缀索引是在底层数据按照Key列排序的基础上,选择前缀的一个或多个Key列构建的索引,其本质是基于数据排序特性进行二分查找。前缀索引属于内建索引,建表后SelectDB自动创建。
前缀索引没有专门的语法去定义,系统会根据建表的Key列字段定义顺序,选取前36个字节所能覆盖的字段作为前缀索引,但当遇到VARCHAR类型时,前缀索引会直接截断,后面的Key列不再加入前缀索引。
建表时字段定义的顺序尤为重要,它决定了哪些字段会被用作前缀索引,强烈建议您参考如下原则确定Key列顺序:
高频用于过滤条件的、高基数的Key列放在其他字段之前。如Duplicate模型章节的日志场景中,日志时间
log_time
放在错误码error_code
之前。等值过滤条件的Key列放在区间过滤条件的Key列之前。如倒排索引章节的电商场景中,时间
order_time
通常按照区间过滤,放在订单号order_id
之后。普通类型字段放在VARCHAR类型字段之前。如INT类型的Key列放在VARCHAR类的KEY列之前。
使用示例
在倒排索引章节的电商场景中,订单信息表的前缀索引为order_id+order_time
,当查询条件是前缀索引的前缀时(即查询条件包含order_id或同时包含order_id和order_time),可以极大的加快查询速度。如以下两个示例,示例一的查询速度会远高于示例二的查询速度。
示例一
SELECT * FROM orders WHERE order_id = 1829239 and order_time = '2024-02-01';
示例二
SELECT * FROM orders WHERE order_time = '2024-02-01';
下一步
完成本教程的前三个步骤后,您已初步了解SelectDB,并具备设计符合业务需求的数据库表的基本能力。下一步,您可以根据后续指引,了解您业务涉及的具体功能,例如数据迁移、查询外部数据源、版本升级等。更多功能,请参见后续指引。