Postgres 使用一个索引 table 而不是另一个
Postgres using an index for one table but not another
我的应用程序中有三个 table,分别命名为 tableA
、tableB
和 tableC
。 tableA
有 tableB_id
和 tableC_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 毫秒)。
据我所知,table大小差不多。
关于两个查询之间巨大的性能差异有什么想法吗?
更新
Table 尺寸:
tableA: 2061392 行
tableB: 175339 行
tableC: 1888912 行
Postgres 版本 - 9.3.5
查询全文如上。
相关信息来自tables:
- table一个
- 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 是一个单独的 table,否则不相关
- tableC
- id, integer, not null default nextval('tableC_id_seq'::regclass), storage plain
- "tableC_pkey" PRIMARY_KEY, btree (id)
- 栏,字符变化(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 英特尔酷睿 i5
内存:8 GB 1600 MHz DDR3
显卡:Intel HD Graphics 5000 1536 MB
解决方案
看起来像 运行 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_id
在 tableA
中有多行,那么这些 竞争 命令中的任何一个都可以影响性能通过将相关行物理聚集在一起来支持各自的查询:
CLUSTER tableA USING "index_tableA_on_tableB_id";
CLUSTER tableA USING "index_tableA_on_tableC_id";
你不能两者兼得。它是 B
或 C
。 CLUSTER
也做 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 索引的理解以及查询计划的执行方式,您不能不解决确切的问题。
我的应用程序中有三个 table,分别命名为 tableA
、tableB
和 tableC
。 tableA
有 tableB_id
和 tableC_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 毫秒)。
据我所知,table大小差不多。
关于两个查询之间巨大的性能差异有什么想法吗?
更新
Table 尺寸:
tableA: 2061392 行
tableB: 175339 行
tableC: 1888912 行
Postgres 版本 - 9.3.5
查询全文如上。
相关信息来自tables:
- table一个
- tableB_id,整数,无修饰符,存储简单
- "index_tableA_on_tableB_id" btree (tableB_id)
- tableC_id,整数,无修饰符,存储简单,
- "index_tableA_on_tableB_id" btree (tableC_id)
- tableB_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 是一个单独的 table,否则不相关
- "index_tableB_on_lower_foo_tableD" UNIQUE, btree (lower(foo::text), tableD_id)
- id, integer, not null default nextval('tableB_id_seq'::regclass), storage plain
- tableC
- id, integer, not null default nextval('tableC_id_seq'::regclass), storage plain
- "tableC_pkey" PRIMARY_KEY, btree (id)
- 栏,字符变化(255),无修饰符,存储扩展
- "index_tableC_on_tableB_id_and_bar" UNIQUE, btree (tableB_id, bar)
- "index_tableC_on_lower_bar" btree (lower(bar::text))
- id, integer, not null default nextval('tableC_id_seq'::regclass), storage plain
硬件:
OS X 10.10.2
CPU:1.4 GHz 英特尔酷睿 i5
内存:8 GB 1600 MHz DDR3
显卡:Intel HD Graphics 5000 1536 MB
解决方案
看起来像 运行 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 上的索引 语法无效 。你最好 post 在 psql 中 (lower(foo::text))
\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_id
在 tableA
中有多行,那么这些 竞争 命令中的任何一个都可以影响性能通过将相关行物理聚集在一起来支持各自的查询:
CLUSTER tableA USING "index_tableA_on_tableB_id";
CLUSTER tableA USING "index_tableA_on_tableC_id";
你不能两者兼得。它是 B
或 C
。 CLUSTER
也做 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 索引的理解以及查询计划的执行方式,您不能不解决确切的问题。