Apache Doris 倒排索引工作原理:全文检索提速 59 倍,点查提速 14 倍

在传统的 OLAP 场景中,数据库非常擅长处理固定、可预测的大批量聚合查询。例如"计算本季度各品类的平均评分",这类查询可以通过物理分区和排序聚簇,让计算引擎成块地快速读取数据。

然而,在面向用户的分析场景中,查询往往是临时且不可预测的。以服务数百万卖家的电商平台为例:每个卖家登录后台,都只能查看自己的数据。他们发起的查询通常是:

  • 查看我账号下的所有商品评价
  • 统计我上架的某款产品的总销量与收入
  • 在电子产品列表中,筛选出提及'电池续航'的评论

对底层的分析数据库而言,平台需要同时为数百万卖家运行这类查询。每一条查询,都相当于在包含数亿行记录的"大海"里寻找分散的几百条"针"。在缺乏针对性索引的情况下,数据库引擎无法跳过无关数据,只能做昂贵的全表扫描。 这正是传统 OLAP 在处理此类"稀疏扫描"场景时面临的挑战。

为了解决这一问题,部分团队选择在 OLAP 之外叠加 Elasticsearch 架构,但这带来了双引擎维护、数据同步与 Schema 变更等繁重的工作。

那么,能否在 OLAP 引擎内部直接解决稀疏扫描问题

我们基于开源分析型数据库 Apache Doris ,针对包含 1.35 亿条数据 的亚马逊评论数据集进行了 50 高并发测试。结果表明:引入内置倒排索引后,全文检索性能提升 59 倍,商品查找提速 14 倍,多维组合查询提速 10 倍

传统 OLAP 数据库处理稀疏扫描的局限

传统 OLAP 数据库(如 AWS Redshift)之所以在大大规模数据分析中表现优异,主要依赖以下三项关键技术来减少数据扫描量:

  • 列式存储:仅读取查询相关的列,有效降低磁盘 I/O 开销;
  • 物理排序:依据指定列(排序键)对数据进行物理聚簇存放;
  • 数据块映射(Zone Maps):记录每个存储块(Data Block)内数据的最小值与最大值,以此实现数据块级别的过滤(Data Skipping)。

这套机制在处理密集扫描(Dense Scan)场景时非常高效。

以按评论日期(review_date)排序的 1.35 亿条亚马逊评论表为例:

sql 复制代码
CREATE TABLE amazon_reviews (
  review_date INT NULL,
  marketplace VARCHAR(20) NULL,
  customer_id BIGINT NULL,
  review_id VARCHAR(40) NULL,
  product_id VARCHAR(10) NULL,
  product_parent BIGINT NULL,
  product_title VARCHAR(500) NULL,
  product_category VARCHAR(50) NULL,
  star_rating SMALLINT NULL,
  helpful_votes INT NULL,
  total_votes INT NULL,
  vine BOOLEAN NULL,
  verified_purchase BOOLEAN NULL,
  review_headline VARCHAR(500) NULL,
  review_body STRING NULL
)
DUPLICATE KEY(review_date)
DISTRIBUTED BY HASH(review_date) BUCKETS 16
PROPERTIES ("compression" = "ZSTD");

1、密集扫描:查询本季度平均评分

由于数据已按 review_date 排序,本季度的所有记录在物理存储上高度集中。引擎读取 Zone Maps 记录的日期范围,即可跳过绝大多数非目标时间段的数据块,仅扫描少量相关的聚簇数据。

2、稀疏扫描:查询产品 B00BGGDVOO 的所有评论

该产品在数年间陆续产生的 14,200 条评论,在物理存储上呈离散分布,此时原有机制失效。

  • 排序键无法辅助筛选:数据按日期排序,无法为产品 ID 提供检索索引;
  • Zone Maps 无法裁剪数据块:数据集中包含约 2,000 万个独立产品 ID,导致几乎每一个数据块都混杂了该产品的数据,Zone Maps 的极值范围失去了过滤意义。

最终,引擎只能强行扫描全部 1.35 亿行数据 ,仅为了提取其中占比约 0.01% 的目标行。在 50 个并发线程的负载下,此类查询的延迟会大幅上升,系统吞吐量(QPS)受到严重限制。

物理存储的顺序是单维度的。如果将排序键调整为 product_id,虽然能加速产品查找,但会导致基于日期范围的范围查询退化为全表扫描。物理数据组织方式无法同时适配两种截然不同的访问模式。

为此,许多团队不得不引入 Elasticsearch + OLAP 的双引擎架构:由 Elasticsearch 负责稀疏点查与文本搜索,分析型数据库负责大批量聚合。

但这种架构带来了显著的工程复杂度:团队不仅需要搭建与维护实时数据同步链路,还必须在两套系统间同步 Schema 变更,并编写上层路由逻辑分发查询。在生产环境中,这类基础设施会产生较高的运维与一致性保障成本。

倒排索引应对稀疏扫描的技术机制

稀疏扫描的核心挑战在于:如何在不扫描全表的前提下,精准定位物理存储上离散分布的目标行

倒排索引通过建立"数据值到行位置(Row ID)"的直接映射打破了传统 OLAP 的全扫描逻辑。在实际查询中,数据引擎不再需要依次解压与读取数据块,而是先查询索引获取符合条件的 Row ID 列表,再精准跳转至对应位置读取数据。

针对不同的字段类型与查询模式,倒排索引在底层采用了差异化的数据结构与实现机制:

字符串精确匹配:Posting List(倒排列表)

传统的正向存储记录的是"行到值"的映射(按行查找属性);而倒排索引记录的是"值到行"的映射(按属性查找行)。比如:

正向索引

  • 第 1 行 → customer_id: A1B2C3D4
  • 第 2 行 → customer_id: E5F6G7H8
  • 第 3 行 → customer_id: A1B2C3D4

倒排索引

  • A1B2C3D4[Row 1, 3]
  • E5F6G7H8[Row 2]

当执行 WHERE customer_id = 'A1B2C3D4' 时,引擎检索倒排列表可直接获取目标集合 [1, 3],从而彻底跳过对第 2 行及其他非匹配数据的扫描。

数值与范围过滤:BKD 树

对于 star_rating(星级)、helpful_votes(有用投票数)等数值型字段,如果直接对每个数值建立简单的 Posting List,在进行范围检索时效率有限。为此,系统采用了适合多维数值空间的 BKD 树 数据结构。

BKD 树通过递归划分子空间,将连续的数值划分并组织为有序的叶子数据块:

  • 点查询 (如 WHERE star_rating = 5):通过 BKD 树的节点二分查找,毫秒级定位到目标叶子节点并提取 Row ID。
  • 范围查询 (如 WHERE helpful_votes BETWEEN 100 AND 500):依据树结构的区间重叠判定,仅遍历与 [100, 500] 存在交集的叶子节点,快速收集符合条件的 Row ID 集合。

非结构化文本检索:分词器 + Posting List

文本搜索首先将文本内容拆分为可检索的词条,再为每个词条建立对应的倒排列表:

  • 原文:"电池续航差,耗电快"

  • 分词与倒排映射:

    • "电池"[Row 1, 3, 7, 15, 23]
    • "续航"[Row 1, 8, 15, 23]
    • "差"[Row 1, 2, 5, 7, 15, 19]

当执行全文搜索(如搜索包含 "电池" 且包含 "差" 的评论)时,引擎无需逐行匹配字符串,只需提取这两个词条对应的 Posting List,通过高效的位图运算获取交集,即可精确定位目标文本行。

不同的查询模式对倒排列表(Posting List)的组合方式各有不同:

基准测试:Apache Doris 倒排索引性能验证

为了验证倒排索引对稀疏查询性能的提升,我们在包含 1.35 亿条数据的亚马逊评论数据集上进行测试。在基础表结构之上,新增了 7 个倒排索引,覆盖高频查询列:客户 ID、产品 ID、评论 ID、星级评分、有用投票数,以及两个文本列。

sql 复制代码
CREATE TABLE IF NOT EXISTS amazon_reviews (
  review_date INT NULL,
  marketplace VARCHAR(20) NULL,
  customer_id BIGINT NULL,
  review_id VARCHAR(40) NULL,
  product_id VARCHAR(10) NULL,
  product_parent BIGINT NULL,
  product_title VARCHAR(500) NULL,
  product_category VARCHAR(50) NULL,
  star_rating SMALLINT NULL,
  helpful_votes INT NULL,
  total_votes INT NULL,
  vine BOOLEAN NULL,
  verified_purchase BOOLEAN NULL,
  review_headline VARCHAR(500) NULL,
  review_body STRING NULL,
   INDEX idx_customer_id (customer_id) USING INVERTED,
   INDEX idx_product_id (product_id) USING INVERTED,
   INDEX idx_review_id (review_id) USING INVERTED,
   INDEX idx_star_rating (star_rating) USING INVERTED,
   INDEX idx_helpful_votes (helpful_votes) USING INVERTED,
   INDEX idx_review_body (review_body) USING INVERTED PROPERTIES("parser" = "english"),
   INDEX idx_review_headline (review_headline) USING INVERTED PROPERTIES("parser" = "english")
)
DUPLICATE KEY(review_date)
DISTRIBUTED BY HASH(review_date) BUCKETS 16
PROPERTIES ("compression" = "ZSTD");

测试环境配置

  • 硬件节点:16 核 CPU,128GB 内存(单计算节点)
  • 数据集:1.35 亿行,26GB(基础大小),47GB(含索引)
  • 并发线程:50 个并发线程
  • 测试工具:JMeter(关闭 SQL 缓存以获取真实引擎执行耗时)

测试查询集

测试选取了 7 个具备代表性的稀疏查询场景,涵盖精确匹配、范围筛选、多维组合过滤、全文检索以及文本情感筛选:

sql 复制代码
-- Q3:客户历史记录(基于 customer_id 倒排列表)
SELECT product_category, COUNT(*) as reviews, AVG(star_rating) as avg_rating,
      SUM(helpful_votes) AS total_helpful
FROM amazon_reviews
WHERE customer_id = 53096570
GROUP BY product_category ORDER BY reviews DESC;
​
-- Q4:产品评论分析(基于 product_id 倒排列表)
SELECT star_rating, COUNT(*) as count, AVG(helpful_votes) as avg_helpful
FROM amazon_reviews
WHERE product_id = 'B00BGGDVOO'
GROUP BY star_rating ORDER BY star_rating DESC;
​
-- Q5:按 ID 精确查找评论(基于 review_id 倒排列表)
SELECT * FROM amazon_reviews WHERE review_id = 'R1NQ5RXN1LZ0YW';
​
-- Q6:基于评分与投票数的范围筛选(基于 BKD 树)
SELECT product_category, COUNT(*) as reviews, AVG(helpful_votes) as avg_helpful
FROM amazon_reviews
WHERE star_rating >= 4 AND helpful_votes >= 50 AND review_date >= 16071
GROUP BY product_category ORDER BY avg_helpful DESC LIMIT 10;
​
-- Q7:多维组合筛选(倒排列表 + BKD 树)
SELECT product_category, product_title, star_rating, helpful_votes, review_headline
FROM amazon_reviews
WHERE customer_id = 16378095
 AND star_rating <= 2
 AND helpful_votes >= 10
ORDER BY helpful_votes DESC LIMIT 20;
​
-- Q9:全文搜索(分词倒排索引 MATCH_ALL)
SELECT product_id, product_title, star_rating, review_headline,
      LEFT(review_body, 200) AS review_snippet
FROM amazon_reviews
WHERE review_body MATCH_ALL 'battery life poor'
 AND product_category = 'Electronics'
ORDER BY star_rating ASC LIMIT 20;
​
-- Q16:文本情感筛选(分词处理 + BKD 树 + 聚合)
SELECT product_id, product_title, COUNT(*) as negative_reviews,
      AVG(star_rating) as avg_rating
FROM amazon_reviews
WHERE product_category = 'Electronics'
 AND review_body MATCH_ALL 'defective broken'
 AND star_rating <= 2
GROUP BY product_id, product_title ORDER BY negative_reviews DESC LIMIT 20;

测试结果分析

数据表明,倒排索引在绝大多数稀疏查询场景下展现出明显的加速效果:

  • Q3(客户历史记录查询) :由于该列为 BIGINT 类型,原生的列式扫描本身效率较高,因此索引带来的额外提升较小。但随着数据集规模增大,倒排索引依然具备潜在收益。
  • Q5(按 ID 精确查找) :性能提升达到 156 倍。全表扫描 1.35 亿行查找单条记录效率较低,而通过倒排列表可以直接定位目标行,大幅减少了 I/O 开销。
  • Q9(全文搜索) :性能提升达 59 倍。若不使用分词倒排索引,全文检索需要逐行进行字符串匹配运算;借助索引后可实现毫秒级响应。

资源开销与适用场景评估

建立倒排索引需要付出一定的存储与写入性能代价。在新增 7 个倒排索引后:

  • 存储占用:数据文件总大小从 26 GB 增加至 47 GB(增幅约 82%),额外存储开销主要来自两个大文本列的倒排索引;
  • 写入速度:数据摄入耗时从 488 秒增加至 519 秒(写入速度下降约 6%)。

若仅对 ID 列和数值列建立索引,存储与写入开销通常显著降低。建议根据业务查询特征,合理选择建索引的列:

结束语

传统 OLAP 数据库针对大批量数据的聚集扫描进行了深度优化,在聚合统计场景下表现优秀。但在处理涉及特定产品查找、客户历史查询及文本检索等稀疏定位需求时,受限于数据扫描范围,查询耗时往往较高。

倒排索引通过引入"值到行位置"的直接映射机制(精确匹配采用倒排列表、范围查询采用 BKD 树、文本采用分词索引),使引擎具备了精准定位目标行数据的能力。基于 1.35 亿条亚马逊评论数据集的并发测试结果显示,引入倒排索引后,稀疏查询与全文检索场景的运行速度可提升 10 至 59 倍

采用该技术方案需权衡一定的存储和写入开销,这些开销主要取决于文本列索引的规模。对于目前正在维护"分析型数据库 + Elasticsearch"双架构的团队,评估并引入 OLAP 内置倒排索引能力是简化技术栈与提升稀疏查询性能的有效路径。

欢迎阅读 Apache Doris 官方文档下载使用;若希望低运维成本开箱即用,也可考虑使用 SelectDB云服务。

相关推荐
码农颜1 小时前
5.1.1 原⼦性
数据库·mysql
机建狂魔1 小时前
Codex 接入第三方模型 API 实战:以 Mimo 为例
java·服务器·数据库·ai·ai编程·codex
山峰哥2 小时前
数据库工程与SQL调优:从慢查询到秒级响应的实战之路
java·开发语言·数据库·sql·深度优先·启发式算法
moMo2 小时前
# 向量数据库入门:从"关键词匹配"到"语义理解"
数据库
ACP广源盛139246256732 小时前
DeepSeek‑V4‑Flash 公测@ACP#昇腾 950 国产算力组合落地,国产 PCIe 交换芯片 IX9104 有哪些硬件机会
大数据·数据库·人工智能·分布式·单片机·嵌入式硬件·microsoft
进击的女IT3 小时前
通过mysql中的Data目录恢复数据库数据
数据库·mysql
yuezhilangniao3 小时前
AI工具全家桶:从开发到运维,从数据库到产品经理-含魔塔社区简介
运维·数据库·人工智能
gb42152873 小时前
ChromaDB向量数据库的特点
数据库
IvorySQL4 小时前
PostgreSQL 日报| GiST 索引扫描可见性缺陷(8 月 3 日)
数据库·人工智能·postgresql·开源