基于湖库一体架构,统一管理结构化、半结构化与非结构化等多模态数据,一个系统承载事务处理、实时分析与 AI 工作负载。
避免回表
更新时间:2026-04-16 00:11:36
在一些极端的场景中,我们可能会遇到一个查询单次的读行量并不多,但是这个查询的 QPS 非常的高,导致整体上这个查询产生非常大的数据读取。如果机器的 I/O 能力有限,这时候磁盘 I/O 可能会被击穿。
以下面场景为例:
CREATE TABLE T1 (C1 INT, C2 INT, C3 INT, C4 INT, ... ,C16 INT);
CREATE INDEX IDX_C1 ON T1 (C1);
EXPLAIN SELECT C3, C4 FROM T1 WHERE C1 = 1;
| =========================================
|ID|OPERATOR |NAME |EST. ROWS|COST|
-----------------------------------------
|0 |TABLE SCAN|T1(IDX_C1)|4 |229 |
=========================================
Outputs & filters:
-------------------------------------
0 - output([T1.C3], [T1.C4]), filter(nil),
access([T1.C3], [T1.C4]), partitions(p0)
上面这个查询使用 C1 列上的索引后,已经得到了最优的 Query Range。该查询每次在 IDX_C1 上扫描的数据量很少。另外,该查询需要得到 C3, C4 列的结果,而 IDX_C1 上没有保存这两列的值,所以它还要回查主表。每次查询执行,会在 IDX_C1 上产生少量的顺序读取;还会在主表上产生少量的随机读取。当查询的 QPS 非常高时,对主表会产生大量的随机读取,这会极大的消耗系统的 I/O 资源。针对这种场景,我们需要考虑额外一个优化方向:创建覆盖索引避免回查主表。以上面的查询为例,我们可以创建一个覆盖索引来优化。
CREATE INDEX IDX_C1_C3_C4 ON T1 (C1, C3, C4);
EXPLAIN SELECT C3, C4 FROM T1 WHERE C1 = 1;
| ===============================================
|ID|OPERATOR |NAME |EST. ROWS|COST|
-----------------------------------------------
|0 |TABLE SCAN|T1(IDX_C1_C3_C4)|4 |46 |
===============================================
Outputs & filters:
-------------------------------------
0 - output([T1.C3], [T1.C4]), filter(nil),
access([T1.C3], [T1.C4]), partitions(p0)
这个查询访问 IDX_C1_C3_C4 时既有最优的 Query Range,也不需要回查主表。实际查询执行时,只会对索引产生少量的顺序扫描。这能极大减少查询产生的 I/O。
一般来说,需要创建覆盖索引的场景需要满足几个特征:
- 数据库的 I/O 压力非常大
- 大量的 I/O 是若干高频查询回查主表产生的随机 I/O 导致的
- 高频查询需要读取的列并不多
在这种情况下,我们可以对这些高频查询针对性地创建覆盖索引进行优化。利用覆盖索引减少回表的场景相对而言是比较少的。