8

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

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

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

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

我正在运行 PostgreSQL 9.2.1。

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

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


Here'e解释分析结果: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 错误邮件列表

4

2 回答 2

10

正如 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;

如果您为“Roles”.“Name”设置更高的统计目标,然后为 ANALYZE,您还可以为您的原始查询制定一个好的计划。例如:

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

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

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

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

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

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

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

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

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

于 2012-11-20T18:09:14.313 回答
5

终于成功尝试

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

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

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 的回答后来澄清了解决方法失败的原因:使用CTE而不是子查询。
或者在使用大查询消除坏情况之前检查角色的存在。

这给 PostgreSQL 留下了关于使用LIMIT.

最近有许多关于查询计划的错误报告LIMIT我在这里引用 Simon Riggs 对其中一份报告的评论:

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

我们需要做点什么。

第一次尝试没有成功

我错过了评论中已经提到join_collapse_limit的@Craig。所以这是有限的用途:

重新排序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_limitor的设置geqo_threshold?非常低的设置可能会阻止计划者重新排序您的JOIN条款,这可能会解释您的问题。

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

于 2012-11-16T11:47:07.977 回答