Postgres 使用一个索引 table 而不是另一个

Postgres using an index for one table but not another

我的应用程序中有三个 table,分别命名为 tableAtableBtableCtableAtableB_idtableC_id 的字段,两者都有索引。 tableB 有一个带索引的字段 footableC 有一个带索引的字段 bar

当我执行以下查询时:

select * 
from tableA 
  left outer join tableB on tableB.id = tableA.tableB_id 
where lower(tableB.foo) = lower(my_input)

真的很慢(~1 秒)。

当我执行以下查询时:

select * 
from tableA 
   left outer join tableC on tableC.id = tabelA.tableC_id 
where lower(tableC.bar) = lower(my_input)

它真的很快(~20 毫秒)。

据我所知,table大小差不多。

关于两个查询之间巨大的性能差异有什么想法吗?


更新

Table 尺寸:


标签信息

Postgres 版本 - 9.3.5

查询全文如上。

解释计划 - tableB tableC

相关信息来自tables:

硬件:


解决方案

看起来像 运行 vacuum,然后分析所有三个 tables 解决了这个问题。 运行 命令后,慢速查询开始使用“index_patients_on_foo_tableD”。

首先,您的 LEFT JOIN 左边的谓词 table 抵消,被迫表现得像 [INNER] JOIN。替换为:

SELECT *
FROM   tableA a
JOIN   tableB b ON b.id = a.tableB_id
WHERE  lower(b.foo) = lower(my_input);

或者,如果您确实希望 LEFT JOIN 包含来自 tableA 所有 行:

SELECT *
FROM   tableA a
LEFT   JOIN tableB b ON b.id = a.tableB_id
                    AND lower(b.foo) = lower(my_input);

我想你想要第一个。

像您 posted (lower(foo::text)) 上的索引 语法无效 。你最好 post 在 psql 中 \d tbl 的逐字输出,就像我反复评论的那样。索引定义中转换 (foo::text) 的 shorthand 语法需要更多括号,或使用标准语法:cast(foo AS text):

  • Create index on first 3 characters (area code) of phone field?

但这也没有必要。您可以只使用 foo 的数据类型 (character varying(255))。当然,数据类型 character varying(255) 在 Postgres 中很少有意义 开始。对 255 个字符的奇怪限制源自其他 RDBMS 中的限制,这些限制不适用于 Postgres。详情:

  • Refactor foreign key to fields

尽管如此。此类查询的完美索引是 B 上的多列索引 - 如果(且仅当)您从中得到 index-only scans

CREATE INDEX "tableB_lower_foo_id" ON tableB (lower(foo), id);

然后您可以删除大部分已被取代的索引 "index_tableB_on_lower_foo"tableC.
相同 table A on tableB_id and tableC_id.

中的(更重要!)索引涵盖了其余部分

如果每个 tableB_id / tableC_idtableA 中有多行,那么这些 竞争 命令中的任何一个都可以影响性能通过将相关行物理聚集在一起来支持各自的查询:

CLUSTER tableA USING "index_tableA_on_tableB_id";
CLUSTER tableA USING "index_tableA_on_tableC_id";

你不能两者兼得。它是 BCCLUSTER 也做 VACUUM FULL 会做的一切。但一定要先阅读详情:

  • Optimize Postgres timestamp query range

并且不要使用 大小写混合标识符,有时会引用,有时不会。这非常令人困惑,并且势必会导致错误。只使用合法的小写标识符 - 那么无论您是否将它们加双引号都没有关系。

另一件事是您将索引列查询为 lower() ,当查询为 运行ning.

时也可以创建部分索引

如果您总是将列查询为 lower(),那么您的列应该被索引为 lower(column_name),如:

create index idx_1 on tableb(lower(foo));

还有,你看过执行计划了吗?如果您能看到它是如何查询 tables.

的,这将回答您所有的问题

老实说,这有很多因素。最好的解决方案是研究索引,特别是在 Postgres 中,这样您就可以了解它们是如何工作的。这是一个整体性的主题,如果对它们的工作原理有最低限度的了解,您无法真正回答所有问题。

例如,Postgres 在查询 运行 之前有一个初始的 "lets look at these tables and see how we should query them"。它查看所有 table,每个 table 有多大,存在哪些索引等等,然后计算出查询应该如何 运行。然后它执行它。通常,这是错误的。引擎错误地确定了如何执行它。

很多计算都是根据汇总的 table 统计数据完成的。您可以重置任何 table 的汇总 table 统计数据,方法是:

vacuum [table_name];

(这有助于防止死行导致的膨胀)

然后:

analyze [table_name];

我并不总是看到这项工作,但它经常有帮助。

无论如何,最好的办法是:

a) 研究 Postgres 索引(简单的写法,不要复杂到离谱) b) 研究查询的执行计划 c) 使用您对 Postgres 索引的理解以及查询计划的执行方式,您不能不解决确切的问题。