postgresql Postgres对一个表使用索引,而对另一个表不使用索引

slmsl1lt  于 2022-12-29  发布在  PostgreSQL
关注(0)|答案(3)|浏览(143)

我的应用中有三个表,分别称为tableAtableBtableCtableA包含tableB_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大小差不多。
关于这两个查询之间巨大的性能差异,您有什么想法吗?

更新

表格尺寸:

  • 表A:2061392行
  • 表B:175339行
  • 表C:1888912行

postgresql-performance标记信息
Postgres版本-9.3.5
质询全文见上文。
解释计划-tableBtableC
表格中的相关信息:

  • 表A
  • tableB_id,整数,无修饰符,纯存储
  • "表B_id上的索引表A" b树(表B_id)
  • tableC_id,整数,无修饰符,存储格式,
  • "索引表A_on_表B_id" b树(表C_id)
  • 表B
  • ID,整数,非空默认nextval('tableB_id_seq'::regclass),存储格式
  • "表B_主键"主键,b树(标识)
  • foo,字符变化(255),无修饰符,扩展存储
  • "索引表B_on_lower_foo_tableD"唯一,b树(低位(foo::文本),表D_id)
  • tableD是一个单独的表,在其他方面是不相关的
  • 表C
  • ID,整数,非空默认nextval('tableC_id_seq'::regclass),存储格式
  • "表C_pkey"主键,b树(标识)
  • 条形图,字符变化(255),无修饰符,扩展存储
  • "索引表C_在表B_id_and_bar上"唯一,b树(表B_id,bar)
  • "下部条形图上的索引表C" b树(下部(条形图::文本))

硬件:

  • 操作系统X 10.10.2
  • CPU:1.4 GHz英特尔酷睿i5
  • 内存:8 GB 1600 MHz DDR3
  • 显卡:英特尔核芯显卡5000 1536 MB

溶液

看起来运行Vacuum然后对所有三个表进行Analysis修复了这个问题。运行命令后,缓慢的查询开始使用"Index_Patients_on_Foo_TableD"。

toe95027

toe950271#

另一件事是,您将索引列查询为lower(),这也可以在查询运行时创建部分索引。
如果您将始终以lower()的形式查询列,则您的列应该以lower(column_name)的形式进行索引,如下所示:

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

另外,您看过执行计划了吗?如果您能看到它是如何查询表的,那么这将回答您所有的问题。
老实说,这有很多因素。最好的解决办法是研究索引,特别是在Postgres中,这样你就可以看到它们是如何工作的。这是一个整体性的问题,你不可能只了解它们是如何工作的。
例如,Postgres在查询运行之前有一个初始的"让我们看看这些表,看看我们应该如何查询它们"。它检查所有的表,每个表有多大,存在什么索引,等等,然后计算出查询应该如何运行。然后它执行它。通常,这就是问题所在。引擎错误地确定了如何执行它。
许多此类计算都是根据汇总表统计信息完成的。您可以通过执行以下操作重置任何表的汇总表统计信息:

vacuum [table_name];

(this有助于防止死行膨胀)
然后:

analyze [table_name];

我并不总是看到这种工作,但往往它的帮助。
无论如何,最好的办法是:
a)研究Postgres索引(简单的文章,而不是复杂得离谱的东西)b)研究查询的执行计划c)利用您对Postgres索引和查询计划如何执行的理解,您无法帮助解决确切的问题。

dsekswqp

dsekswqp2#

对于初学者,您的LEFT JOIN会被 * left * 表上的 predicate 抵消,并被迫像[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)

  • 是否在电话字段的前3个字符(区号)上创建索引?

但是这也是不必要的。你可以只使用foo的数据类型(character varying(255))。当然,数据类型character varying(255)在Postgres中几乎没有意义。255个字符的奇怪限制来自于其他RDBMS的限制,而这些限制在Postgres中并不适用。详细信息:

  • 重构字段的外键

尽管如此,这种查询的最佳索引应该是B上的多列索引--当(且仅当)您从中得到index-only scans时:

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

然后你可以删除被取代最多的索引"index_tableB_on_lower_foo",同样的方法也适用于tableC
其余部分由表A中关于tableB_idtableC_id的(更重要的!)索引涵盖。
如果每个tableB_id/tableC_idtableA中有多个行,那么这些 * 竞争 * 命令中的任何一个都可以通过将相关行物理地聚集在一起来改变性能,以有利于相应的查询:

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

你不能两者都有。它要么是B,要么是CCLUSTER也能做VACUUM FULL所能做的一切。但是一定要先阅读详细信息:

  • 优化Postgres时间戳查询范围

不要使用混合大小写的标识符,有时候加引号,有时候不加引号。这非常容易混淆,而且肯定会导致错误。只使用合法的小写标识符--然后不管你是否用双引号把它们括起来。

axr492tv

axr492tv3#

让postgres来完成它的工作,查询计划器总是选择最优化的执行计划,在这个场景中使用索引不是这样。

相关问题