为什么在非常简单的散列连接案例中没有使用连接键上的索引?

hea*_*low 1 postgresql performance index execution-plan

我有两个表:

create table animal
(
    aid integer,
    cid integer,
    aname varchar(255) default NULL::character varying,
    species text
);

create table daily_feeds
(
    aid integer,
    zid integer,
    shift integer,
    menu integer
);
Run Code Online (Sandbox Code Playgroud)

和一个查询:

SELECT
aname,
shift
FROM animal
NATURAL JOIN daily_feeds
WHERE menu = 1;
Run Code Online (Sandbox Code Playgroud)

餐桌动物包含大约 40000 行,表 daily_feeds 包含大约 80000 行,每只动物 2 行。

我认为在用于连接animal.aiddaily_feeds.aid的列上添加索引可能会导致不同的执行计划并提高性能。

create index aaid on animal (aid);

create index daid on daily_feeds using btree (aid);
Run Code Online (Sandbox Code Playgroud)

这不会发生。这是为什么?

显然,这是一个非常简单且学术性的示例,我试图用它来了解有关数据库查询优化的更多信息。

编辑:

添加没有索引时的执行计划:

Hash Join  (cost=1439.24..2488.23 rows=499 width=10) (actual time=11.701..55.260 rows=487 loops=1)
    Hash Cond: (animal.aid = daily_feeds.aid)
    ->  Seq Scan on animal  (cost=0.00..694.00 rows=40000 width=10) (actual  time=0.007..20.775 rows=40000 loops=1)
    ->  Hash  (cost=1433.00..1433.00 rows=499 width=8) (actual time=11.590..11.590 rows=487 loops=1)
        Buckets: 1024  Batches: 1  Memory Usage: 28kB
        ->  Seq Scan on daily_feeds  (cost=0.00..1433.00 rows=499 width=8) (actual time=0.011..11.218 rows=487 loops=1)
            Filter: (menu = 1)
            Rows Removed by Filter: 79513
Planning time: 0.124 ms
Execution time: 55.521 ms
Run Code Online (Sandbox Code Playgroud)

EDIT2:这里的另一个问题:

由于我使用了两个索引,因此我也可以将它们设为聚集索引,通过辅助属性对表进行物理排序。

cluster animal using aaid;
cluster daily_feeds using daid;
Run Code Online (Sandbox Code Playgroud)

如果我这样做,执行计划就不会再改变了。如果两个表都按键列排序,合并连接不是更有效的连接策略吗?

我不太确定 Postgres 在这里的行为。

RDF*_*ozz 6

提供有关所使用的实际查询计划的信息会有所帮助。

一般来说,数据库引擎必须使用这两个索引来标识两个表中具有匹配aid值的行;然后,查找daily_feeds表中的所有实际行以查看是否menu为 1,然后查找其中的行animal以获取aname值。

如果要返回大量行,并且这些是您的完整表,那么很可能已经决定首先简单地加载表并扫描它们以找到匹配的行并执行在menu检查(在这一点aname已经存在且可用)。


让我注意到,我最熟悉 SQL Server 使用索引的方式。以下某些内容可能不直接适用于 PostgreSQL。但是,大多数基础知识似乎都匹配,我不会深入研究细节,以免出错。

优化器可以通过三种方式在daily_feeds. 它可以简单地浏览整个表格;它可以使用aid索引来寻找 的特定值或值范围aid,然后使用索引信息去表中正确的页来获取行;或者,它可以将索引视为一个表,并对其进行扫描(可能查找整个表中的行,如果它需要的所有数据都不在索引中)。

优化器在确定如何获取所需数据时必须考虑许多因素。一个因素是为了获取数据可能需要加载的页面数量。从磁盘加载页面是数据库最耗时的任务之一。因此,如果它可以限制它可能需要的页数,那么应该可以加快查询速度。

对于这个查询,我们需要在animal(aidaname) 中找到两列,在daily_feeds( aid, menu, shift) 中找到三列。

它有两种方法来识别它感兴趣的行:aidinanimal匹配aidin 的daily_feeds行和 1 的行menu。我猜统计数据会表明(当然,外键关系会表明,如果它确实存在)所有或几乎所有行都将根据aid=aid检查从两个表中返回。这并不能很好地减少我们正在寻找的行数。但是,menu= 1 会淘汰很多行。由于menu不在索引中,我们必须去表中过滤它。

一旦我们得到了daily_feeds我们想要的行,我们需要找到匹配的行animal来选择aname. 我们可以期望寻找大约 500 行,并且必须假设(最坏的情况)它们在animal数据中均匀分布。我在这里做另一个假设;(即使有一text列)animal表中的行并不是很宽。如果基本表存储在 500 个页面中(忽略text数据;它可能存储在行外,但我们在此查询中不需要它),那么很可能必须加载所有页面才能获得我们需要的行。

现在,有两条路径可以继续:

  • 使用aaid索引animal找到aid我们正在寻找的 500 个值;然后,使用索引,转到数据所在的页面并在每个页面中找到它们。
  • animal直接加载表格,直接扫描aid我们要查找的500个值。

优化器决定使用选项 2。从这个角度来看,这似乎并不奇怪。

注意:引擎可能在迈出第一步之前就决定了整个计划。这menu有时会导致糟糕的计划(例如,如果结果= 1 只带回 5 行,那么使用索引可能会更快)。SQL Server 可以维护列的统计信息,帮助它确定基数(X 列的已知值将限制目标行的程度),以帮助它做出这些决定。PostgreSQL 也有某种统计工具——不确定它是否可以直接比较。