第三步:学习数据库表设计要点

良好的表结构设计不仅能支持丰富的功能需求,还能大幅提升数据库系统的性能、可维护性和可扩展性等,因此数据库的表结构设计至关重要。本文为您介绍在云数据库 SelectDB 版中,设计表结构时需要重点关注的表属性,并帮助您快速掌握如何根据业务场景选择合适的表设计,从而更好的满足业务需求。

重要表属性概览

在业务接入SelectDB时,根据业务场景做好重要表属性的设计,对于构建出能满足业务需求且高性能、易维护的表结构至关重要。以下是SelectDB重要表属性的快速概览。

表属性

是否必选

属性关键作用

详情链接

数据模型

不同的数据模型适用于不同的业务场景:Unique模型支持主键唯一性约束,可满足灵活高效的数据更新需求。

Duplicate模型采用追加写模式,适用于明细数据的高性能分析场景。

Aggregate模型支持数据预聚合,专注于数据聚合统计场景。

数据模型

分桶

分桶用于将数据分散到集群中的不同节点,以充分利用分布式系统的分而治之的能力来管理和查询海量数据。

分区

分区能够根据指定字段(如时间、地域等)将原始表划分为多个子表,以便于对数据进行分区管理和查询,同时利用分区裁剪来提升查询速度。

索引

索引能够快速地过滤或定位数据,从而大幅提升查询性能。

索引

数据模型

数据模型的合理选择,对于能否满足数据分析场景的功能需求和性能要求具有决定性影响。不同的模型适用于不同的业务场景。此处仅对各个模型进行简要介绍,旨在帮助您快速了解数据模型,以便于您进行模型选择。更多详情,请参见专题介绍文章数据模型

基础概念

SelectDB中,数据通过表(Table)的形式在逻辑层面进行组织和管理。每张表由行(Row)和列(Column)组成。行表示数据表中的一行数据,而列用于描述该行数据中的不同字段。

列可以分为以下两大类:

  • Key列:Key列是指建表语句中被关键字UNIQUE KEYAGGREGATE KEYDUPLICATE 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模型,适用于通过预聚合提升查询性能的聚合统计场景。例如网站流量分析、定制化报表等数据分析场景。

  • count(*)查询的支持不友好。

  • Value列的聚合方式是固定的。

快速使用模型

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),用于对数据进行物理划分,分桶是系统进行数据打散、移动等操作的最小单元。

image

分区与分桶的关联
  • 一个分桶仅属于一个分区,而一个分区则包含多个分桶。

  • 在建表过程中,如果采用了分区(Partition),则表先按分区规则进行划分,分区内再按指定的分桶规则进行划分;如果未使用分区,则表直接按指定的分桶规则进行划分。

  • 在数据写入过程中,数据首先被划分至相应的分区,随后在分区内依据分桶规则进一步写入至不同的分桶中。分桶是对分区数据的进一步细分,其目的是为了更加均匀地分布数据,从而提升查询效率。

分区(Partition)

SelectDB的存储引擎中,分区是一种数据组织方式,用于将表中的数据按照用户定义的规则划分为多个独立的部分,从而实现数据的逻辑划分。这有助于提升查询效率,同时也使得数据管理更加灵活和便捷。此处仅对分区进行简要介绍,旨在帮助您快速了解分区,以便于您进行分区选型。更多详情,请参见分区动态分区

分区选择指导

SelectDB支持两种分区方式:Range分区和List分区。同时,也提供了简单易用的动态分区功能,以实现对分区的自动化管理。不同的分区方式,适用于不同的业务场景。

分区方式

支持的列类型

指定分区信息方式

适用场景

Range

列类型:DATE、DATETIME、TINYINT、SMALLINT、INT、BIGINT、LARGEINT

支持四种写法:

  1. VALUES [...):定义分区的左闭右开区间。

  2. VALUES LESS THAN (...):仅定义分区上界。下界由上一个分区的上界决定。

  3. BATCH RANGE:批量创建数字类型和时间类型的Range分区,定义分区的左闭右开区间,设定步长。

  4. MULTI RANGE:批量创建Range分区,定义分区的左闭右开区间。

适用于对数据划分区间进行管理,典型的场景是按时间进行分区。

List

列类型:BOOLEAN、TINYINT、SMALLINT、INT、BIGINT、LARGEINT、DATE、DATETIME、CHAR、VARCHAR

支持通过VALUES IN (...)来指定每个分区包含的枚举值。

适用于依据数据的既有类别或固定特性进行数据管理,分区列通常为枚举值,例如根据用户所属地域进行数据划分管理。

注意事项

  • 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中,表的索引的构建方式有内建和自定义两种。对于内建索引,系统会自动创建。而对于自定义索引,需要您在建表时或者建表后,根据需要自行创建。

构建方式

索引类型

支持的查询类型

不支持的查询类型

优势

劣势

内建

前缀索引

  • 等于、不等于查询

  • 范围查询

  • LIKE查询

  • MATCH(关键词、短语)

前缀索引占用的空间相对较小,能够在内存中进行全量缓存,从而实现快速定位数据块,显著提升查询效率。

一个表只能有一个前缀索引。

自定义

倒排索引(推荐)

  • 字符串、数值、日期时间类型的等于、不等于查询、范围查询

  • 字符串类型MATCH(关键词、短语)

  • 文本类型的全文检索

支持的查询类型丰富。支持在建表时、建表后按需创建索引,并支持索引删除。

索引存储空间相对较大。

BloomFilter索引

等于查询

  • 不等于查询

  • 范围查询

  • LIKE查询

  • MATCH(关键词、短语)

索引构建占用的计算和存储资源少。

支持的查询类型少,只支持等于查询。

NGram BloomFilter索引

LIKE查询

  • 等于、不等于查询

  • 范围查询

  • MATCH(关键词、短语)

提高LIKE查询速度,索引构建占用的计算和存储资源少。

只支持LIKE加速。

快速使用索引

倒排索引

SelectDB支持倒排索引,可用于满足文本字段的全文检索、以及普通字段的等值或范围查询,能够快速从大量数据中筛选出满足条件的数据。此处仅简绍如何创建倒排索引,更多信息,请参见倒排索引

建表时创建索引

在电商场景中,根据用户ID、用户地址关键词查询订单信息是高频操作,此时可以在customer_idcustomer_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,并具备设计符合业务需求的数据库表的基本能力。下一步,您可以根据后续指引,了解您业务涉及的具体功能,例如数据迁移、查询外部数据源、版本升级等。更多功能,请参见后续指引