PostgreSQL 查询耗时过长

Posted

技术标签:

【中文标题】PostgreSQL 查询耗时过长【英文标题】:PostgreSQL query taking too long 【发布时间】:2012-11-04 15:28:58 【问题描述】:

我有几亿行的数据库。我正在运行以下查询:

select * from "Payments" as p
inner join "PaymentOrders" as po
on po."Id" = p."PaymentOrderId"
inner join "Users" as u
On u."Id" = po."UserId"
INNER JOIN "Roles" as r
on u."RoleId" = r."Id"
Where r."Name" = 'Moses'
LIMIT 1000

当 where 子句在数据库中找到匹配项时,我会在几毫秒内得到结果,但如果我修改查询并在 where 子句中指定不存在的 r."Name",则需要花费太多时间才能完成。我猜 PostgreSQL 正在对Payments 表(包含最多行)进行顺序扫描,逐一比较每一行。

postgresql 还不够聪明,无法先检查Roles 表是否包含Name 'Moses' 的任何行吗?

Roles 表仅包含 15 行,而 Payments 表包含约 3.5 亿行。

我正在运行 PostgreSQL 9.2.1。

顺便说一句,对相同架构/数据的相同查询在 MS SQL Server 上完成需要 0.024 毫秒。

我会在几个小时内更新问题并发布 EXPLAIN ANALYZE 数据。


这里解释一下分析结果:http://explain.depesz.com/s/7e7


这是服务器配置:

version PostgreSQL 9.2.1, compiled by Visual C++ build 1600, 64-bit
client_encoding UNICODE
effective_cache_size    4500MB
fsync   on
lc_collate  English_United States.1252
lc_ctype    English_United States.1252
listen_addresses    *
log_destination stderr
log_line_prefix %t 
logging_collector   on
max_connections 100
max_stack_depth 2MB
port    5432
search_path dbo, "$user", public
server_encoding UTF8
shared_buffers  1500MB
TimeZone    Asia/Tbilisi
wal_buffers 16MB
work_mem    10MB

我在 i5 cpu(4 核,3.3 GHz)、8 GB RAM 和 Crucial m4 SSD 128GB 上运行 postgresql


更新 这看起来像查询计划器中的错误。在 Erwin Brandstetter 的推荐下,我将其报告给了Postgresql bugs mailing list。

【问题讨论】:

现在,请将普通的EXPLAIN 发布到explain.depesz.com 并在您的帖子中链接到它。它会立即运行。感谢您提供您的 PostgreSQL 版本和有用的详细信息。另一件要尝试的事情:ANALYZE 表格,看看性能是否有所提高。也许你的统计数据完全出来了?您也可以在explain analyze 中看到这一点,这是行数估计完全错误的最常见原因。请在更新时添加评论,因为 SO 不会发送有关问题编辑的通知。 @CraigRinger 非常感谢您的帮助。我已经分析了数据库中的所有表并只执行了解释查询。结果如下:explain.depesz.com/s/hol。但是,解释分析仍在运行,我会在它完成后立即发布结果。再次感谢。 @CraigRinger 对不起,克雷格,我不明白你的意思。我不是以英语为母语的人:)。无论如何,这里解释一下分析结果:explain.depesz.com/s/7e7 知道如何解决这个慢查询吗?再次非常感谢你:) 没关系。我要的是EXPLAIN ANALYZE(或至少EXPLAIN)用于快速查询。您已经展示了问题查询计划是什么。这将有助于查看 PostgreSQL 在名称确实存在时选择什么,以及快速产生结果的查询计划。 我很想看看在Roles.RoleId 上添加UNIQUE 约束(因此UNIQUE 索引)是否有任何效果,如果这与您的数据模型兼容。还请从wiki.postgresql.org/wiki/Server_Configuration 显示您的配置并阅读wiki.postgresql.org/wiki/Slow_Query_Questions。我对您的 join_collapse_limit 特别感兴趣,尽管它应该默认足够高,对这个查询没有影响。 【参考方案1】:

正如 PostgreSQL 社区性能列表上的线程多次建议的那样,您可以通过使用 CTE 强制优化障碍来解决此问题,如下所示:

WITH x AS
(
SELECT *
  FROM "Payments" AS p
  JOIN "PaymentOrders" AS po ON po."Id" = p."PaymentOrderId"
  JOIN "Users" as u ON u."Id" = po."UserId"
  JOIN "Roles" as r ON u."RoleId" = r."Id"
  WHERE r."Name" = 'Moses'
)
SELECT * FROM x
  LIMIT 1000;

如果您为“角色”、“名称”设置更高的统计目标,然后再进行分析,您也可以为您的原始查询制定一个好的计划。例如:

ALTER TABLE "Roles"
  ALTER COLUMN "Name" SET STATISTICS 1000;
ANALYZE "Roles";

如果它预计表中存在较少的匹配行,因为它可能会处理更细粒度的统计信息,它会假设它需要读取更高百分比的表才能在顺序扫描中找到它们。这可能会导致它更喜欢使用索引而不是顺序扫描表。

您还可以通过调整计划器的一些成本常数和缓存假设来为原始查询制定更好的计划。您可以在单个会话中尝试使用SET 命令:

减少random_page_cost。这主要取决于缓存数据的严重程度。给定一个包含数亿行的表,您可能不想低于 2;尽管如果您的数据库中的活动数据集被大量缓存,您可以将其一直减少到seq_page_cost 的设置,并且您可能希望将它们都减少一个数量级。

确保 Effective_cache_size 设置为 shared_buffers 和您的操作系统正在缓存的任何内容的总和。这不会分配任何内存;它只是告诉优化器在大量访问期间索引页保留在缓存中的可能性有多大。与顺序扫描相比,较高的设置会使索引看起来更好。

cpu_tuple_cost 增加到 0.03 到 0.05 范围内的某个位置。我发现默认值 0.01 太低了。我经常通过增加它来获得更好的计划,并且从未见过我建议的范围内的值会导致选择更糟糕的计划。

确保您的work_mem 设置合理。在我运行 PostgreSQL 的大多数环境中,大小在 16MB 到 64MB 之间。这将允许更好地使用哈希表、位图索引扫描、排序等,并且可以完全改变您的计划;几乎总是变得更好。如果您有大量连接,请注意将其设置为产生良好计划的级别 - 您应该考虑到每个连接都可以为它正在运行的查询的每个节点分配这么多内存的事实。 “经验法则”是计算您将在此设置时间max_connections 附近达到峰值。这是使用连接池限制实际数据库连接数是明智之举的原因之一。

如果您找到这些设置的良好组合,您可能希望对您的postgresql.conf 文件进行这些更改。如果您这样做,请密切监控性能回归,并准备调整设置以实现整体负载的最佳性能。

我同意我们需要做一些事情来让优化器远离“有风险”的计划,即使它们看起来平均运行得更快;但是,如果调整您的配置以使优化器更好地模拟每个替代方案的实际成本并不会导致它使用有效的计划,我会感到有些惊讶。

【讨论】:

哇,这是一个很好的答案。谢谢朋友和+1 :)【参考方案2】:

终于成功尝试

我的另一个想法 - 根据评论: 如果在未找到角色的情况下删除 LIMIT 子句,会发生什么情况?我怀疑这会导致快速计划 - 让 LIMIT 成为这里的罪魁祸首。

您可以通过将查询下推到 子查询 并将 LIMIT 仅应用于外部查询(未经测试)来解决您的问题:

SELECT *
FROM  (
   SELECT *
   FROM   "Roles"         AS r  
   JOIN   "Users"         AS u  ON u."RoleId" = r."Id"
   JOIN   "PaymentOrders" AS po ON po."UserId" = u."Id"
   JOIN   "Payments"      AS p  ON p."PaymentOrderId" = po."Id"
   WHERE  r."Name" = 'Moses'
  ) x
LIMIT  1000;

根据评论:@Davita 测试并排除了这种解决方法。 @Kevin's answer 后来澄清了解决方法失败的原因:使用 CTE 而不是子查询。 或者在使用大查询消除不良情况之前检查角色是否存在。

这给 PostgreSQL 留下了关于使用 LIMIT 优化查询的问题。

有很多recent bug reports concerning query plans with LIMIT。我引用 Simon Riggs 对其中一份报告的评论 here:

使用 LIMIT 的非常糟糕的计划很常见。这对我们不利,因为 添加 LIMIT 通常/应该使查询更快,而不是更慢。

我们需要做点什么。

第一次尝试没有成功

我错过了 @Craig 在 cmets 中已经提到的 join_collapse_limit。所以这是有限的用处:

重新排序JOIN 子句有什么效果吗?

SELECT *
FROM   "Roles"         AS r  
JOIN   "Users"         AS u  ON u."RoleId" = r."Id"
JOIN   "PaymentOrders" AS po ON po."UserId" = u."Id"
JOIN   "Payments"      AS p  ON p."PaymentOrderId" = po."Id"
WHERE  r."Name" = 'Moses'
LIMIT  1000

相关:join_collapse_limitgeqo_threshold 的设置不是偶然弄乱的吗? 非常低的设置可能会阻止规划器重新排序您的 JOIN 子句,这可能会解释您的问题。

如果这不能解决问题,我会尝试在"Roles"(Name) 上创建一个索引。并不是说这仅对 15 行有任何意义,但我会尝试消除无效统计或成本参数(甚至是错误)使规划者认为对“角色”进行顺序扫描比实际更昂贵的怀疑。

【讨论】:

如果更改连接顺序改变了某些东西,我会认为这是 PostgreSQL 中的错误。 感谢 Erwin,我尝试了您的建议,我将 join_collapse_limit 更改为 16,然后更改为 64,但我得到了相同的结果。我尝试了您的查询,但没有任何改变。我在 Roles.Name 上添加了 B-TREE 索引,但没有变化:|我真的很困惑。 @a_horse_with_no_name:对于少量表,规划器会重新排序 JOIN 以符合其最佳估计。但是 JOIN 的最佳顺序是 O(n!) 问题。所以有一个限制,之后使用更通用的优化尝试。当然,这对于 4 张桌子来说根本不是问题。查询执行显然从错误的一端开始,因此我在 join_collapse_limit 的黑暗中开枪 - 现在添加了 geqo_threshold @Davita:抱歉,至少它排除了一些可能的(理论上的)原因。你也看过geqo_threshold吗? (稍后添加。) @ErwinBrandstetter 再次感谢 Erwin,我刚刚尝试将 geqo_threshold(虽然我不知道它是什么 :D)设置为 40 但仍然是相同的查询计划。

以上是关于PostgreSQL 查询耗时过长的主要内容,如果未能解决你的问题,请参考以下文章

Mongodb 查询执行耗时过长

Mongodb 查询耗时过长

SQL 查询耗时过长

Microsoft SQL Server:错误的查询执行计划耗时过长

PostGIS 查询耗时过长。 >400ms

由于 where 子句中的 <>,查询耗时过长