我们有一个表foo_tbl
(名称obscated,相同的数据类型和DDL):
CREATE TABLE public.foo_tbl (
id int8 NOT NULL,
foo_id varchar(11) NOT NULL,
foo_date timestamptz NULL,
-- ... other unrelated columns ...
CONSTRAINT pk_footbl PRIMARY KEY (id)
);
CREATE INDEX idx_1_2cols ON public.foo_tbl USING btree (foo_date, foo_id); -- initial index
CREATE INDEX idx_2_1col ON public.foo_tbl USING btree (foo_id); -- added later, when the query is slow
我们有一个很大的查询,它使用foo_id
连接7个表和这个表,并得到foo_date
。示例(真实的查询要大得多):
select b.bar_code, f.foo_date from bar_tbl b join foo_tbl f on b.bar_id = f.foo_id limit 100;
如果没有使用foo_tbl
的连接,查询速度很快(〈2s)。
使用foo_tbl
添加连接后,尽管使用索引idx_1_2cols
在foo_tbl
上运行“仅索引扫描”(查询中仅使用此表的这2列),但查询速度要慢得多(〉15秒)。这是表的EXPLAIN ANALYZE
结果:
{
"Node Type": "Index Only Scan",
"Parent Relationship": "Inner",
"Parallel Aware": false,
"Scan Direction": "Forward",
"Index Name": "idx_1_2cols",
"Relation Name": "foo_tbl",
"Schema": "public",
"Alias": "f",
"Startup Cost": 0.42,
"Total Cost": 2886.11,
"Plan Rows": 1,
"Plan Width": 20,
"Actual Startup Time": 12.843,
"Actual Total Time": 13.068,
"Actual Rows": 1,
"Actual Loops": 1200,
"Output": ["f.foo_date", "f.foo_id"],
"Index Cond": "(f.foo_id = (b.bar_id)::text)",
"Rows Removed by Index Recheck": 0,
"Heap Fetches": 0,
"Shared Hit Blocks": 2284772,
"Shared Read Blocks": 0,
"Shared Dirtied Blocks": 0,
"Shared Written Blocks": 0,
"Local Hit Blocks": 0,
"Local Read Blocks": 0,
"Local Dirtied Blocks": 0,
"Local Written Blocks": 0,
"Temp Read Blocks": 0,
"Temp Written Blocks": 0,
"I/O Read Time": 0.0,
"I/O Write Time": 0.0
}
为了研究,我们创建了单列索引idx_2_1col
,查询再次快速(〈3s)。当EXPLAIN时,计划器选择新索引而不是旧索引进行“索引扫描”:
{
"Node Type": "Index Scan",
"Parent Relationship": "Inner",
"Parallel Aware": false,
"Scan Direction": "Forward",
"Index Name": "idx_2_1col",
"Relation Name": "foo_tbl",
"Schema": "public",
"Alias": "f",
"Startup Cost": 0.42,
"Total Cost": 0.46,
"Plan Rows": 1,
"Plan Width": 20,
"Actual Startup Time": 0.007,
"Actual Total Time": 0.007,
"Actual Rows": 1,
"Actual Loops": 1200,
"Output": ["f.foo_date", "f.foo_id"],
"Index Cond": "((f.foo_id)::text = (b.bar_id)::text)",
"Rows Removed by Index Recheck": 0,
"Shared Hit Blocks": 4800,
"Shared Read Blocks": 0,
"Shared Dirtied Blocks": 0,
"Shared Written Blocks": 0,
"Local Hit Blocks": 0,
"Local Read Blocks": 0,
"Local Dirtied Blocks": 0,
"Local Written Blocks": 0,
"Temp Read Blocks": 0,
"Temp Written Blocks": 0,
"I/O Read Time": 0.0,
"I/O Write Time": 0.0
}
那么,为什么在这种情况下索引扫描比只索引扫描快?为什么只索引扫描这么慢?
备注:
- 已在
EXPLAIN ANALYZE
查询之前VACUUM ANALYZE
foo_tbl
不是最大的,只有几十万条记录,连接中的一些表包含数百万条记录。- DBS是Amazon Aurora PostgreSQL兼容13.5(非无服务器)
2条答案
按热度按时间gab6jxml1#
多列索引中最左边的列是应该查询的列。在您的示例中,只返回
foo_date
,并且只对第二列foo_id
进行值检查。doc很清楚这一点,甚至指出在这种情况下,整个索引将被扫描,而规划器很可能会扫描整个表。
多列B树索引可以与涉及索引列的任何子集的查询条件一起使用,但是当索引的前确切的规则是前导列上的等式约束,加上第一列上不具有等式约束的任何不等式约束,将用于限制要扫描的索引部分。在索引中检查对这些列右侧的列的约束,因此它们可以保存对表本身的访问,但不会减少必须扫描的索引部分。
您可以尝试切换索引中的列,或者通过在第二个索引中包含日期来创建covering index,以避免触及表。
c3frrgcw2#
由于尚未提供bar_tbl的DDL,查询规划器显然选择使用idx_1_2cols,因为它同时具有所需的两个列,并且规划器估计使用索引比扫描基表更有效(最肯定的是,因为阅读表将需要更多的块读取,因为不需要的列)。问题是,连接是在foo_id上,但是索引前缀是foo_date。将索引顺序更改为(foo_id,foo_date),查询将运行得更快。
添加idx_2_1col提高了性能,因为bar_id和foo_id之间的连接可以使用索引高效地进行,即使还需要从基表读取数据来满足查询。