连接表上的索引

Posted

技术标签:

【中文标题】连接表上的索引【英文标题】:Indexes on join tables 【发布时间】:2016-03-10 18:39:24 【问题描述】:

在 Google 上搜索连接表索引时,我得到了this question。

现在,我认为它在接受的答案中提供了一些虚假信息,或者我不明白一切是如何运作的。 给定以下表格(在 PostGreSQL 9.4 上运行):

CREATE TABLE "albums" ("album_id" serial PRIMARY KEY, "album_name" text)
CREATE TABLE "artists" ("artist_id" serial PRIMARY KEY, "artist_name" text)
CREATE TABLE "albums_artists" ("album_id" integer REFERENCES "albums", "artist_id" integer REFERENCES "artists")

我试图复制上述问题中的场景,首先在 albums_artists 表的两列上创建一个索引,然后为每一列创建一个索引(不保持索引两列)。

当使用 EXPLAIN 命令进行正常的传统选择时,我会期待非常不同的结果,如下所示:

SELECT "artists".* FROM "test"."artists"
    INNER JOIN "test"."albums_artists" ON ("albums_artists"."artist_id" = "artists"."artist_id")
    WHERE ("albums_artists"."album_id" = 1)

但是,当实际对其运行解释时,每种情况的结果都完全相同(每列都有一个索引,而两列都有一个索引)。

我一直在阅读有关索引的 PostGreSQL 文档,但它对我得到的结果没有任何意义:

Hash Join  (cost=15.05..42.07 rows=11 width=36) (actual time=0.024..0.025 rows=1 loops=1)
  Hash Cond: (artists.artist_id = albums_artists.artist_id)
  ->  Seq Scan on artists  (cost=0.00..22.30 rows=1230 width=36) (actual time=0.006..0.006 rows=1 loops=1)
  ->  Hash  (cost=14.91..14.91 rows=11 width=4) (actual time=0.009..0.009 rows=1 loops=1)
        Buckets: 1024  Batches: 1  Memory Usage: 1kB
        ->  Bitmap Heap Scan on albums_artists  (cost=4.24..14.91 rows=11 width=4) (actual time=0.008..0.009 rows=1 loops=1)
              Recheck Cond: (album_id = 1)
              Heap Blocks: exact=1
              ->  Bitmap Index Scan on albums_artists_album_id_index  (cost=0.00..4.24 rows=11 width=0) (actual time=0.005..0.005 rows=1 loops=1)
                    Index Cond: (album_id = 1)

当使用由 2 个不同列组成的索引时,我希望在最后一步不会进行索引扫描(因为我只在 WHERE 子句中使用其中一个)。

我正要在 ORM 库中打开一个错误,该错误会为连接表的两列添加一个索引,但现在我不太确定。谁能帮我理解为什么这两种情况下的行为相似,如果有的话,实际上会有什么区别?

【问题讨论】:

【参考方案1】: 在键列上添加 NOT NULL 约束(带 NULL 的元组在这里没有意义) 添加一个 PRIMARY KEY(强制两个键域上的唯一索引) 作为 FK 查找的支持:为 PK 字段添加复合索引以相反的顺序 创建/添加 PK 和索引后,您可能需要分析表(只有关键列有统计信息)
CREATE TABLE albums_artists
    ( album_id integer NOT NULL REFERENCES albums (album_id)
    , artist_id integer NOT NULL REFERENCES artists (artist_id)
    , PRIMARY KEY (album_id, artist_id)
    );

CREATE UNIQUE INDEX ON albums_artists (artist_id, album_id);

观察到的行为背后的原因是规划器/优化器是基于信息的,由启发式驱动。如果没有任何关于给定条件实际需要的行的分数或实际匹配的行的分数(在 JOIN 的情况下)的任何信息,规划器会做出猜测:(例如:范围查询为 10%)。对于小型查询,散列连接将始终是一个成功的方案,它确实意味着从两个表中获取所有元组,但连接本身非常有效。

对于属于键或索引的列,将收集统计信息,因此规划器可以对所涉及的行数做出更现实的估计。这通常会产生一个索引计划,因为这可能需要更少的页面来获取。

外键是一种非常特殊的情况;因为规划者将知道来自引用表的所有值将出现在引用表中。 (即 100%,假设 NOT NULL)

【讨论】:

以上是关于连接表上的索引的主要内容,如果未能解决你的问题,请参考以下文章

内部连接的 SQL 查询执行时间慢

Rails 迁移:重命名表上的索引

如何在内部连接表上强制索引?

小表上的仅索引扫描非常慢

循环不更新表上的值,索引明智

每天晚上被删除的表上的 Mysql 索引