Postgres使用一个表的索引而不是另一个表

时间:2015-02-17 00:38:32

标签: postgresql indexing postgresql-performance

我的应用中有三个表格,称为tableAtableBtableCtableA包含tableB_idtableC_id的字段,两者都有索引。 tableB的字段foo包含索引,tableC的字段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毫秒)。

据我所知,这些表大小相同。

关于两个查询之间巨大的性能差异的任何想法?


更新

表格大小:

  • tableA:2061392行

  • tableB:175339行

  • tableC:1888912行


代码信息

Postgres版本 - 9.3.5

查询的全文在上面。

解释计划 - tableB tableC

表中的相关信息:

  • TABLEA
    • tableB_id,整数,无修饰符,存储平原
      • " index_tableA_on_tableB_id" btree(tableB_id)
    • tableC_id,整数,无修饰符,存储平原,
      • " index_tableA_on_tableB_id" btree(tableC_id)
  • tableB的
    • id,integer,not null default nextval(' tableB_id_seq' :: regclass),storage plain
      • " tableB_pkey" PRIMARY_KEY,btree(id)
    • foo,字符变化(255),无修饰符,存储扩展
      • " index_tableB_on_lower_foo_tableD" UNIQUE,btree(lower(foo :: text),tableD_id)
        • tableD是一个单独的表,否则无关
  • 表C
    • id,integer,not null default nextval(' tableC_id_seq' :: regclass),storage plain
      • " tableC_pkey" PRIMARY_KEY,btree(id)
    • bar,字符变化(255),无修饰符,存储扩展
      • " index_tableC_on_tableB_id_and_bar" UNIQUE,btree(tableB_id,bar)
      • " index_tableC_on_lower_bar" btree(lower(bar :: text))

硬件:

  • OS X 10.10.2

  • CPU:1.4 GHz Intel Core i5

  • 内存:8 GB 1600 MHz DDR3

  • 图形:Intel HD Graphics 5000 1536 MB


解决方案

看起来像运行真空,然后分析所有三个表修复了问题。运行命令后,慢速查询开始使用" index_patients_on_foo_tableD"。

2 个答案:

答案 0 :(得分:1)

对于初学者,您的LEFT JOIN会被表中的谓词抵消,并被迫像[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);

我想你想要第一个。

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

也是不必要的。您可以使用character varying(255)的数据类型(foo)。当然,Postgres中的数据类型character varying(255) 很少有意义。对255个字符的奇怪限制来自其他RDBMS中的限制,这些限制不适用于Postgres。详细说明:

尽管如此。这种查询的完美索引是B上的多列索引 - 如果(并且仅当)你得到index-only scans

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

然后,您可以删除大部分被取代的索引"index_tableB_on_lower_foo"tableC相同。
其余部分由AtableB_idtableC_id中的(更重要!)索引覆盖。

如果tableA / tableB_id / tableC_id中有多行,那么这些竞争命令中的任何一个都可以摆动性能以支持相应的查询通过将相关行物理聚类在一起:

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

你不能兼得。它可以是BCCLUSTER也会执行VACUUM FULL所做的所有事情。但请务必先阅读详细信息:

不要使用混合大小写标识符,有时引用,有时不引用。这非常令人困惑,必然会导致错误。单独使用合法的小写标识符 - 然后,如果您双重引用它们并不重要。

答案 1 :(得分:1)

另一件事是您将索引列查询为lower(),这也可以在查询运行时创建部分索引。

如果您始终将该列查询为lower(),则应将您的列编入索引为lower(column_name),如下所示:

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

另外,你看过执行计划了吗?如果你能看到它是如何查询表格的话,这将回答你的所有问题。

老实说,这有很多因素。最好的解决方案是研究INDEXES,特别是在Postgres中,这样你就可以看到它们是如何工作的。这是一个整体主题,你可以通过对它们如何工作的最小理解来真正回答你所有的问题。

例如,Postgres有一个初始的"让我们看一下这些表,看看我们应该如何查询它们#34;在查询运行之前。它查看所有表,每个表有多大,存在哪些索引等,然后确定查询应该如何运行。然后它执行它。通常情况下,这是错误的。引擎错误地确定了如何执行它。

很多计算都是在汇总表统计中完成的。您可以通过执行以下操作重置任何表的汇总表统计信息:

vacuum [table_name];

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

然后:

analyze [table_name];

我一直看不到这项工作,但往往有帮助。

ANyway,最好的选择是:

a)研究Postgres索引(SIMPLE写的,不是一些非常复杂的东西) b)研究查询的执行计划 c)通过了解Postgres索引以及查询计划的执行方式,您无法解决确切的问题。