表格存储提供二级索引和多元索引两种索引能力,适用于不同的查询场景。本文介绍如何选择索引类型、设计多元索引以及优化查询性能。
索引类型
表格存储的宽表模型按主键有序存储数据,支持主键点查和主键范围查询。当业务需要按属性列查询时,主键查询无法满足需求,而使用过滤器(Filter)在大数据量下需要扫描大量数据,效率较低。
查询效率与底层扫描的数据量正相关,过滤器慢的原因是符合条件的数据过于分散,需要扫描大量数据后才能筛选出结果。表格存储提供两类索引,分别从两个思路解决这一问题:
让符合条件的数据不再分散:二级索引将指定列作为索引表主键,通过数据冗余把符合条件的数据预先排在一起,查询时直接定位并扫描。
加快筛选速度:多元索引底层建立倒排索引,记录每个值对应的主键集合(Value→主键列表),查询时直接通过倒排索引获取符合条件的主键,加快从海量数据中筛选的效率。
二级索引
二级索引相当于一张索引表,将指定的属性列作为主键重新排序。它与数据表一样按主键有序存储,查询能力和数据规模都与数据表相同,区别只在于主键不同。查询时直接通过索引表的主键定位数据,效率与数据表主键查询相当。
二级索引分为全局二级索引和本地二级索引,两者在同步方式、主键要求和一致性方面有所不同(强一致指写入后立即可读到最新数据,最终一致指写入后有短暂延迟才能读到):
|
对比项 |
全局二级索引 |
本地二级索引 |
|
同步方式 |
异步同步,写入数据表后自动同步到索引表 |
同步写入,数据表和索引表同时更新 |
|
第一列主键 |
可以是数据表的任意主键列或预定义列 |
必须与数据表的分区键相同 |
|
数据可见延迟 |
毫秒级 |
实时 |
|
读取一致性 |
最终一致 |
强一致 |
二级索引适合查询维度固定的场景,每种查询维度需要一个独立的索引。以存储文件哈希值的数据表为例,主键列为文件路径(FilePath),属性列为 MD5 和 SHA1:
|
FilePath(主键) |
MD5 |
SHA1 |
|
oss://abc/1.txt |
a1b2c3… |
d4e5f6… |
通过主键可以查询文件对应的哈希值,但无法通过 MD5 或 SHA1 反查文件名。此时为该表创建一张以 MD5 为主键的全局二级索引(SHA1 同理):
|
MD5(主键) |
FilePath(主键) |
|
a1b2c3… |
oss://abc/1.txt |
为保证索引表主键的唯一性,二级索引会自动将数据表的原主键列添加到索引表的主键中。因此上述索引表的主键实际为 MD5 加 FilePath。
多元索引
多元索引是一系列数据结构的组合,底层基于倒排索引、BKD-Tree 等结构,支持多条件组合查询、全文检索、模糊查询、地理位置查询和统计聚合。一个多元索引即可覆盖多种查询条件的任意组合,无需为每种组合单独创建索引。例如订单表包含 16 个字段,需要按售货员、产品类型、价格范围等任意组合查询时,只需创建一个多元索引并将查询字段添加进去即可。
索引选择
无需索引的场景
以下场景不需要创建索引:
二级索引与多元索引对比
二级索引和多元索引在查询灵活度、性能和功能支持上有明显差异,可根据下表选择合适的索引类型:
|
对比项 |
二级索引 |
多元索引 |
|
查询灵活度 |
只能使用创建索引时指定的索引组合查询,其他组合需创建新的索引表 |
可对索引字段做任意组合查询 |
|
查询性能 |
通过索引键定位到对应分片,性能佳 |
需要查询所有分片(可通过路由键优化) |
|
大范围扫描 |
支持,性能与数据表一致 |
支持,但性能低于数据表和二级索引 |
|
数据可见延迟 |
全局二级索引毫秒级,本地二级索引实时 |
秒级 |
|
分词查询 |
不支持 |
支持 |
|
地理位置查询 |
不支持 |
支持 |
|
数据一致性 |
全局二级索引最终一致,本地二级索引强一致 |
最终一致 |
选择建议:查询维度固定、追求低延迟或强一致时,选择二级索引;查询维度多变,或需要全文检索、模糊查询、地理位置查询时,选择多元索引。
索引组合方案
数据规模很大时,灵活的查询意味着更高的成本。以万亿行数据为例,仅用数据表(不建索引)成本低但查询受限,多元索引查询灵活但费用较高,二级索引成本较低但只适合固定维度查询。
超大规模数据通常带有时间属性(如设备监控数据、用户行为数据),可以采用以下组合方案,在查询灵活性和成本之间取得平衡:
元数据表 + 多元索引,全量数据表 + 二级索引或不建索引。元数据表是产生数据的主体表(如设备信息表、用户信息表),数据量相对较小但查询维度多,适合建立多元索引。全量数据表数据量大但查询维度固定,使用二级索引或主键查询即可。
热数据 + 多元索引,冷数据 + 二级索引或不建索引。很多场景只需对热数据做多维度查询,对冷数据采取固定维度查询即可,冷热分离能提供更高的性价比。多元索引支持数据生命周期(TTL),可以通过多元索引 TTL 区分热数据和冷数据。更多信息,请参见多元索引生命周期。
多元索引设计
字段类型规划
多元索引中不同类型字段的底层数据结构不同,选择合适的字段类型可以显著提升查询性能。建议在向数据表写入数据时,根据查询需求提前规划字段类型:
|
查询需求 |
推荐索引类型 |
底层结构 |
示例字段 |
|
精确等值、枚举、多词查询 |
Keyword |
FST + 倒排索引 |
Type、Status、UserId |
|
范围查询 |
Long |
BKD-Tree |
价格、时间戳 |
是否将数字字段转为字符串取决于查询需求:需要对该字段做精确匹配(如按 Type、Status、UserId 等值筛选)时,在数据表中保存为字符串类型并索引为 Keyword 类型,才能使用精确查询(TermQuery)或多词精确查询(TermsQuery,类似 SQL 的 IN 操作);需要做范围查询时,保留 Long 类型。
如果已使用了不合适的字段类型且不便修改存量数据,可以使用动态修改Schema功能平滑升级,或使用虚拟列实现字段类型调整。
索引预排序
如果大多数查询按相同的排序模式返回结果,可以通过设置预排序节省排序时间。命中数据量越大,预排序带来的性能提升越明显。默认情况下,多元索引按数据表的所有主键进行预排序。目前仅支持通过 SDK 设置自定义预排序。
索引路由优化
默认情况下,多元索引根据数据表的所有主键对数据进行哈希分区,查询时需要访问索引引擎的所有分区。如果查询条件中始终包含某个特定字段(例如电商场景中的用户 ID),可以将该字段设置为路由键,改变数据分布,让路由键值相同的数据落在确定的一个或多个分区上。使用路由键有以下好处:
显著减少长尾查询:未使用路由键时每个查询都会访问所有分区,整体延迟取决于最慢的分区,单个分区的毛刺或网络卡顿都会拖慢整体请求。
支持更高的 QPS(每秒查询量):带路由的请求仅访问部分分区,没有读放大,对集群资源消耗较少。
规模无上限:路由键设计合理时,多元索引的规模无上限。
使用路由键的注意事项:
路由键的值应尽量多样,同一个路由键下的数据量不要太多(例如不超过 1 亿行)。如果单个路由键下数据过多,可以将多个不变的字段拼接为路由键。
设置路由键后,如果查询时不指定路由键,会访问所有分区,但不影响查询结果的完整性。
查询 QPS 不高或数据量小于 2 亿行时,无需使用路由优化。
逻辑字段与物理字段映射
当系统中有大量用户、每个用户的列名各不相同时,所有用户的个性化字段加起来会远超多元索引的最大字段数限制。此时可以用一批固定的物理字段让所有用户复用。
复用原理:不同用户的数据存储在不同行,同一个物理字段在不同用户的行中可以承载不同的逻辑字段。例如物理字段 keyword_1 在用户 1 的行中存储字段 a,在用户 2 的行中存储字段 b。再通过一张 meta 映射表记录每个用户的字段对应关系即可。
假设每个用户最多使用 10 个 Keyword 类型的索引字段,设计步骤如下:
索引设计:按单个用户需要的字段数创建固定的物理字段。例如每个用户最多 10 个字段,则创建
keyword_1至keyword_10(如需 Long 类型再加long_1至long_10),以及其他业务必要的非个性化字段。无论有多少用户,多元索引只需要这一批物理字段。准备 meta 映射表:记录每个用户的逻辑字段名到物理字段名的映射。例如用户 1 的字段 a、b、c 分别映射为
keyword_1、keyword_2、keyword_3;用户 2 的字段 b、c、d 分别映射为keyword_1、keyword_2、keyword_3。两个用户复用了相同的物理字段,但因为数据在不同行,互不影响。映射表数据量不大时,建议缓存到内存中。按映射读写数据:写入时根据映射表将用户的逻辑字段值写入对应的物理字段;查询时根据映射表转换查询条件,例如用户 2 查询 b=4 且 d=5,转化为
keyword_1=4 且keyword_3=5。
多元索引查询优化
选择合适的查询方式
多元索引提供多种查询方式,根据查询需求选择合适的方式可以获得更好的性能:
|
查询需求 |
推荐方式 |
说明 |
|
精确等值查询 |
精确查询(TermQuery) |
Keyword 类型字段使用此方式。不要误用匹配查询(MatchQuery),否则会多一道分词处理,性能较差。 |
|
分词文本全文检索 |
匹配查询(MatchQuery)/ 短语匹配查询(MatchPhraseQuery) |
用于分词(将文本切分为词语)的 Text 类型字段。 |
|
任意子串模糊匹配( |
模糊分词 + 短语匹配查询(MatchPhraseQuery) |
比通配符查询(WildcardQuery)性能更好。 |
简化查询条件
如果查询条件过多、嵌套过深或 TermsQuery 中的元素过多,查询延迟可能会偏高。精简查询条件,只保留必要的筛选项。服务端会自动改写并优化查询,一般无需额外关注;如果查询延迟偏高,请联系表格存储技术支持优化查询。
深度翻页与 Token 编码
深度翻页推荐使用 Token 翻页方式。持久化存储 Token(byte[] 类型)时,必须使用 Base64 编码将其转换为字符串。直接使用 new String(token) 进行字符串编码会导致 Token 内容丢失。