问题的回答
PostgreSQL在pg_statistic中维护每列的直方图,并假设列之间的统计独立性,在为多列过滤条件估计选择性时。 当你在高度相关的列上进行过滤时——例如在车辆数据库中,city和state_code或make和model——计划者乘以单个选择性,从而大幅低估实际行数。 这种基数错误通常导致优化器更倾向于嵌套循环连接,而不是哈希或合并连接,导致大型表上的性能灾难。
为了解决此问题,您需要使用CREATE STATISTICS创建一个扩展统计对象,它构建多元相关数据。具体而言,dependencies类型跟踪列之间的功能依赖关系,使得计划者可以识别出按state_code = 'CA'进行过滤已经限制了city的值为加利福尼亚,从而避免乘法错误。
-- 为相关列创建扩展统计 CREATE STATISTICS stats_vehicle_make_model ON make, model FROM vehicles; -- 填充统计信息 ANALYZE vehicles;
生活中的情况
一个物流平台在处理一个跟踪发货的仪表板时遇到困难,该仪表板将50百万行的shipments表与customers进行连接。 查询通过origin_state和origin_city进行过滤,其中95%的行在'Springfield'这个城市实际上是在'IL'中,但是计划者假设只有2%的发货独立匹配这两个谓词。 它估计了500行,并选择了嵌套循环连接,这导致在数百万的客户记录中迭代,并在90秒后超时。
一个考虑的解决方法是在会话中通过SET enable_nestloop = off完全禁用嵌套循环连接。 这强制执行哈希连接,并在3秒内完成这个特定查询,但带来了严重的风险:全局配置更改会传播到连接池中,可能会影响其他在小表上表现良好的合法嵌套循环计划。此外,此解决方法需要在查询之前更改应用程序级代码以设置参数。
另一个选项是创建一个复合索引(origin_city, origin_state)。虽然这改善了索引选择,但并没有解决基数低估的问题;计划者仍然认为索引扫描中会产生少量行,并保持嵌套循环策略,仅通过覆盖索引更快地执行它。此外,宽复合索引消耗了4GB的额外磁盘空间,并减慢了高速度shipments表的写入操作。
最终,团队通过运行CREATE STATISTICS stats_origin_correlation ON origin_city, origin_state FROM shipments,随后执行ANALYZE,以部署扩展统计。此方法无需重写查询,并且增加的存储开销微乎其微。部署后,计划者正确估计了45000行,并选择了哈希连接,将查询延迟减少到400毫秒,同时保留了不相关工作负载的最佳计划。
候选人常常错过的内容
ANALYZE命令如何刷新扩展统计,为什么在创建后统计对象可能看起来未被使用?
ANALYZE仅在明确调用目标表时计算扩展统计,或在统计对象存在后自动清理期间处理表时计算。许多候选人认为CREATE STATISTICS会立即对计划产生影响,但目录表pg_statistic_ext和pg_statistic_ext_data在下一个分析周期之前仍然为空。因此,计划者继续使用单列直方图和独立性假设,直到通过ANALYZE shipments;填充多元数据。您可以通过检查pg_stats_ext视图中的非空dependencies或ndistinct值来验证使用情况。
在CREATE STATISTICS中,dependencies和ndistinct之间有何功能性差异,以及哪些查询模式从中受益?**
Dependencies捕获功能关系,其中一列决定另一列(例如,zip_code决定city),直接修正WHERE子句选择性估计。Ndistinct计算列组的确切不同组合数,这改善了GROUP BY和DISTINCT估计,而不是过滤选择性。候选人常常将这两者混淆,在包含相关列的GROUP BY上的慢查询中创建dependencies,或反之亦然。为了获得最佳效果,指定两种类型:CREATE STATISTICS stats_complex WITH (dependencies, ndistinct) ON (...).
为什么扩展统计可能无法帮助使用OR条件跨相关列的查询?
扩展统计目前只对发生选择性乘法的AND子句提供帮助。当您使用OR进行过滤(例如,city = 'Springfield' OR state = 'IL')时,PostgreSQL使用公式P(A) + P(B) - P(A ∩ B)来计算选择性,并且它无法将依赖系数应用于交集项,因为统计信息跟踪连接(而不是分离)的联合选择性。候选人经常忽略这一限制,试图使用CREATE STATISTICS来修复基于OR的基数错误,这需要重写查询(例如,拆分为UNION ALL分支)或部分索引。