我拥有数亿行的数据库。我正在运行以下查询:
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
表(包含最多行)进行顺序扫描,逐行比较每一行。
如果Roles
表包含Name
'Moses'
的任何行,那么postgresql是否足够聪明?
角色表仅包含15行,而付款包含约3.5亿。
我正在运行PostgreSQL 9.2.1。
顺便说一句,同一模式/数据上的相同查询需要0.024毫秒才能在MS SQL Server上完成。
我会在几个小时内更新问题并发布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报告了它。
答案 0 :(得分:9)
正如在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 :(得分:5)
我的另一个想法 - 根据评论:
如果您在没有找到角色的情况下删除 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已在评论中提到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_limit
或geqo_threshold
的设置?
极低的设置可能会阻止规划人员重新排序您的JOIN
条款,这可能会解释您的问题。
如果这不能解决问题,我会尝试在"Roles"(Name)
上创建一个索引。并不是说只有15行才有意义,但我会试图消除无效统计或成本参数(甚至是错误)使计划者相信“角色”的顺序扫描比它更昂贵的怀疑。