【问题标题】:Bad cardinality for tables with composite primary key具有复合主键的表的基数错误
【发布时间】:2013-09-26 15:29:42
【问题描述】:

当通过复合(两列)主键连接两个表时,我在查询计划中得到错误的基数估计。示例:

CREATE TABLE t1 AS SELECT x, x*2 AS x2 FROM generate_series(0, 1000) AS x;
ALTER TABLE t1 ADD PRIMARY KEY(x, x2);
ANALYZE t1;

CREATE TABLE t2 AS SELECT x, x*2 AS x2 FROM generate_series(0, 1000) AS x;
ALTER TABLE t2 ADD FOREIGN KEY (x, x2) REFERENCES t1(x,x2);
ANALYZE t2;

EXPLAIN ANALYZE
SELECT *
FROM t1 JOIN t2 USING (x, x2)

 QUERY PLAN                                                                                                    
 ------------------------------------------------------------------------------------------------------------- 
 Hash Join  (cost=30.02..52.55 rows=1 width=8) (actual time=0.660..1.551 rows=1001 loops=1)                    
   Hash Cond: ((t1.x = t2.x) AND (t1.x2 = t2.x2))                                                              
   ->  Seq Scan on t1  (cost=0.00..15.01 rows=1001 width=8) (actual time=0.021..0.260 rows=1001 loops=1)       
   ->  Hash  (cost=15.01..15.01 rows=1001 width=8) (actual time=0.620..0.620 rows=1001 loops=1)                
         Buckets: 1024  Batches: 1  Memory Usage: 40kB                                                         
         ->  Seq Scan on t2  (cost=0.00..15.01 rows=1001 width=8) (actual time=0.019..0.230 rows=1001 loops=1) 
 Total runtime: 1.679 ms    

计划期望返回一行,但实际上返回了 1001 行。这在简单查询中不是问题,但是在进行复杂查询时会导致查询计划非常慢。如何帮助查询优化器做得更好?

【问题讨论】:

  • 问题是主键和外键被过度指定:对于两个表,x2 的功能完全依赖于 x1。这使优化器感到困惑;它无法知道隐含的依赖关系。仅加入 t1.x = t2.x 会给出正确的估计。 CREATE TABLE t1 AS SELECT x/100 AS x, x%100 AS x2 FROM generate_series(0, 10000) AS x;(与 t2 相同)也给出了正确的估计值。
  • @joop 数据库可以知道连接不会丢弃任何行,因为外键用于连接。
  • 我知道这一点。但可能是优化器假设每个关键元素都会向键空间添加熵(可能是启发式的错误选择/顺序)。顺便说一句:向 t2 添加主键无济于事。我的 div/mod 技巧确实改变了预期的行数。
  • 如果 postresql 允许,您可以使用其他东西作为主键,并在这两个键的组合上放置唯一索引。

标签: sql postgresql cardinality sql-execution-plan


【解决方案1】:

使用其中一列完全依赖于另一列的复合主键是一种“有趣”的设计。

无论如何,PostgreSQL 目前假设每一列的选择性是相互独立的,因此将它们相乘(不管它们是否在同一个索引中,即使它是主键索引),我不这样做'不知道有什么好办法。

您可以使用这种迂回说法更接近真正的选择性:

EXPLAIN ANALYZE
SELECT *
FROM t1 JOIN t2 on (t1.x=t2.x and t1.x2 between t2.x2 and t2.x2);

【讨论】:

  • 第二列并不真正意味着依赖于第一列。我只是想不出更好的方法来创建测试数据。欢迎提出建议!
【解决方案2】:

另一种创建真正正交关键元素的方法:

CREATE TABLE t1 AS SELECT x/100 AS x, x%100 AS x2 FROM generate_series(0, 10000) AS x;
ALTER TABLE t1 ADD PRIMARY KEY(x, x2);
ANALYZE t1;

CREATE TABLE t2 AS SELECT x/100 AS x, x%100 AS x2 FROM generate_series(0, 10000) AS x;
ALTER TABLE t2 ADD PRIMARY KEY (x, x2) ; -- added PK
ALTER TABLE t2 ADD FOREIGN KEY (x, x2) REFERENCES t1(x,x2);

ANALYZE t2;

【讨论】:

  • 谢谢,这确实是创建测试数据的更好方法。
猜你喜欢
  • 2013-04-30
  • 1970-01-01
  • 2011-04-01
  • 2012-05-18
  • 2011-07-03
  • 2014-06-07
  • 2014-07-31
  • 2010-12-05
  • 2016-07-01
相关资源
最近更新 更多