
前言
技术原理
1
索引推荐流程
Where条件中的 =, >, <, between, in等列
Order By的排序列
Group By的聚合列
MIN,MAX函数列
Join的Condition列
2、构建 Candidate Index
从IndexableColumn中构建出所有可能的Candidate Index
Candidate Index分为单列索引和联合索引,单列索引包括所有Indexable Column,联合索引以一定规则组合Indexable Column
3、优化器What-If选择最优
利用优化器What-If的能力,将Candidate Index逐一评估,通过{CPU cost + IO cost}衡量代价,最终选择出使得SQL执行代价最低的Candidate Index
2
优化器What-if能力
启动代价:读取到第一条元组前花费的代价,比如索引扫描节点的启动代价就是读取目标表的索引页,获取到第一个元组的代价。
运行代价:获取全部元组的代价。
总代价:二者之和。
方案实现
1
总体流程

2、智能化索引推荐
采用通用的索引推荐流程,注册planner_hook,遍历查询树,构造索引项,依赖优化器的What-If能力得到结果。

2
详细设计
针对一条SQL,我们利用内核构造的查询树,精确找到哪些列可以成为索引,制造出索引候选项,交由优化器选择。

最佳实践
1
从RDS控制台进行可视化操作

2
实操步骤
1、创建表
CREATE TABLE t( a INT, b INT );INSERT INTO t SELECT s, 99999 - s FROM generate_series(0,99999) AS s;ANALYZE t;所生成的表包含以下各行:a | b-------+-------0 | 999991 | 999982 | 999973 | 99996...99997 | 299998 | 199999 | 0
如果希望索引推荐分析查询并提出索引编制建议但不实际执行查询,将EXPLAIN关键字作为SQL语句的前缀,示例如下:
postgres=# EXPLAIN SELECT * FROM t WHERE a < 10000;QUERY PLAN---------------------------------------------------------------------------------Seq Scan on t (cost=0.00..1693.00 rows=9983 width=8)Filter: (a < 10000)Result (cost=0.00..0.00 rows=0 width=0)One-Time Filter: '** plan (using Index Adviser) **'::text-> Index Scan using "<1>t_a_idx" on t (cost=0.42..256.52 rows=9983 width=8)Index Cond: (a < 10000)(6 rows)
postgres=# EXPLAIN SELECT * FROM t WHERE a = 100;QUERY PLAN----------------------------------------------------------------------------Seq Scan on t (cost=0.00..1693.00 rows=1 width=8)Filter: (a = 100)Result (cost=0.00..0.00 rows=0 width=0)One-Time Filter: '** plan (using Index Adviser) **'::text-> Index Scan using "<1>t_a_idx" on t (cost=0.42..2.64 rows=1 width=8)Index Cond: (a = 100)(6 rows)
postgres=# EXPLAIN SELECT * FROM t WHERE b = 10000;QUERY PLAN----------------------------------------------------------------------------Seq Scan on t (cost=0.00..1693.00 rows=1 width=8)Filter: (b = 10000)Result (cost=0.00..0.00 rows=0 width=0)One-Time Filter: '** plan (using Index Adviser) **'::text-> Index Scan using "<1>t_b_idx" on t (cost=0.42..2.64 rows=1 width=8)Index Cond: (b = 10000)(6 rows)
可通过psql命令行查询index_advisory表内存储的索引编制建议,示例如下:
postgres=# SELECT * FROM index_advisory;reloid | relname | attrs | benefit | original_cost | new_cost | index_size | backend_pid | timestamp--------+---------+-------+---------+---------------+----------+------------+-------------+----------------------------------16438 | t | {1} | 1337.43 | 1693 | 355.575 | 2624 | 79370 | 18-JUN-21 08:55:51.492388 +00:0016438 | t | {1} | 1684.56 | 1693 | 8.435 | 2624 | 79370 | 18-JUN-21 08:59:00.319336 +00:0016438 | t | {2} | 1684.56 | 1693 | 8.435 | 2624 | 79370 | 18-JUN-21 08:59:07.814453 +00:00(3 rows)

通过show_index_advisory()函数获取单个会话的WorkLoad建议,此函数用于获取单个会话的索引推荐(由后端进程ID标识),可通过指定会话的进程ID来调用该函数:
SELECT show_index_advisory( pid );
其中,pid 是当前会话的进程 ID。如果不知道当前会话的进程 ID,则传递值 NULL 也将为当前会话返回结果集。
postgres=# SELECT show_index_advisory(null);show_index_advisory----------------------------------------------------------------------------------------------------------------------------------------------------create index idx_t_a on public.t(a);/* size: 2624 KB, benefit: 3021.99, gain: 1.15167301457103, original_cost: 1693, new_cost: 182.005006313324 */create index idx_t_b on public.t(b);/* size: 2624 KB, benefit: 1684.56, gain: 0.641983590474943, original_cost: 1693, new_cost: 8.4350004196167 */(2 rows)
说明 结果集中每行的表示意义如下:
创建索引推荐建议的索引所需的SQL语句。
索引页的估计大小。
使用索引的总收益(benefit)。
使用索引的增益(gain=benefit/size)。
使用索引之前的平均代价(即执行SQL的预估时间)。
使用索引之后的平均代价(即执行SQL的预估时间)。
通过select_index_advisory视图获取所有会话的WorkLoad建议,此视图包含计算的指标和CREATE INDEX语句,展示当前位于index_advisory表中所有会话的索引编制建议。表t中列a和列b的索引编制建议显示如下:
postgres=# SELECT * FROM select_index_advisory;backend_pid | show_index_advisory-------------+----------------------------------------------------------------------------------------------------------------------------------------------------79370 | create index idx_t_a on public.t(a);/* size: 2624 KB, benefit: 3021.99, gain: 1.15167301457103, original_cost: 1693, new_cost: 182.005006313324 */79370 | create index idx_t_b on public.t(b);/* size: 2624 KB, benefit: 1684.56, gain: 0.641983590474943, original_cost: 1693, new_cost: 8.4350004196167 */(2 rows)
size = MAX(index size of all queries)benefit = SUM(benefit of each query)gain = SUM(benefit of each query) MAX(index size of all queries)
未来展望
支持GIN、GIST、BRIN索引的推荐。BRIN索引为block索引,对于无法评估数据分布的场景无法推荐;GIST是数据聚集后的结果,也需要对数据分布有所了解;
WorkLoad级别的推荐可以更加细化,当前是以benefit做聚合和排序,得出索引推荐,后续可以更加精细化。
作者信息
赵锐,花名:惜元,专注于RDS PostgreSQL内核研发,热爱和分享PostgreSQL数据库相关技术。欢迎有志之士加入RDS产品部!联系邮箱:vogts.wangt@alibaba-inc.com
推荐阅读

点击“阅读原文”查看云数据库RDS PostgreSQL版更多信息






