【问题标题】:"Unfavourable" execution plan with Postgres and PostGISPostgres 和 PostGIS 的“不利”执行计划
【发布时间】:2014-07-01 08:05:58
【问题描述】:

我有以下数据库架构:

CREATE TABLE public.sgclasstab_id67
( 
    oid bigint NOT NULL,
    att_1113 bigint,
    att_1114 bigint,
    att_1115 character varying(500),
    att_1116 character varying(2000),
    att_1578 double precision,
    CONSTRAINT sgclasstab_id67_pkey PRIMARY KEY (oid)
)

CREATE TABLE public.sgclasstab_id68
(
    oid bigint NOT NULL,
    att_1119 bigint,
    att_1139 bigint,
    att_1496 character varying(2000),
    CONSTRAINT sgclasstab_id68_pkey PRIMARY KEY (oid)
)

CREATE TABLE public.sggeofacelist
(
    oid bigint NOT NULL,
    meanid smallint,
    numofislands smallint DEFAULT 0,
    compound smallint DEFAULT 0,
    extra character varying(512),
    crs bigint DEFAULT (-1),
    crsapp bigint DEFAULT (-1),
    version bigint DEFAULT 0,
    feature geometry,
    CONSTRAINT sggeofacelist_pkey PRIMARY KEY (oid),
    CONSTRAINT enforce_dims_feature CHECK (st_ndims(feature) = 3),
    CONSTRAINT enforce_srid_feature CHECK (st_srid(feature) = 0)
)

CREATE TABLE public.sggeopointlist
(
    oid bigint NOT NULL,
    angle double precision,
    meanid smallint,
    crs bigint DEFAULT (-1),
    crsapp bigint DEFAULT (-1),
    origx double precision,
    origy double precision,
    feature geometry,
    CONSTRAINT sggeopointlist_pkey PRIMARY KEY (oid),
    CONSTRAINT enforce_dims_feature CHECK (st_ndims(feature) = 3),
    CONSTRAINT enforce_srid_feature CHECK (st_srid(feature) = 0)
)

列 sgclasstab_id67.att_1114 引用表 sggeofacelist 中的几何,该表仅包含多边形,sgclasstab_id68.att_1139 列引用表 sggeopointlist 中的几何,该表仅包含点几何。两个表都可以包含数十万个几何图形,其中只有一小部分与上述表相关。所有几何都使用 GIST 索引。

现在,当我运行以下查询时

UPDATE sgclasstab_id68 SET att_1496 = (
    SELECT t3943814704643.att_1115 
    FROM sgclasstab_id67 t3943814704643, sggeofacelist t3943863539361, sgclasstab_id68 t3943875447103, sggeopointlist t3943875522916 
    WHERE ((t3943814704643.att_1114=t3943863539361.oid )) 
        AND ((t3943875447103.att_1139=t3943875522916.oid )) 
        AND ((t3943863539361.feature && (t3943875522916.feature) AND ST_Intersects(t3943863539361.feature,(t3943875522916.feature)))) 
        AND (t3943863539361.oid=t3943814704643.att_1114)  
        AND sgclasstab_id68.oid = t3943875447103.oid 
    LIMIT 1
)

它确实永远运行(我在 4 天后取消了它)。看看执行计划,这并不奇怪:

Update on sgclasstab_id68  (cost=0.00..1076.63 rows=100 width=736)
  ->  Seq Scan on sgclasstab_id68  (cost=0.00..1076.63 rows=100 width=736)
        SubPlan 1
          ->  Limit  (cost=0.70..10.48 rows=1 width=516)
                ->  Nested Loop  (cost=0.70..10.48 rows=1 width=516)
                      ->  Nested Loop  (cost=0.55..10.18 rows=1 width=524)
                            ->  Nested Loop  (cost=0.29..9.33 rows=1 width=5482)
                                  ->  Seq Scan on sgclasstab_id67 t3943814704643  (cost=0.00..1.01 rows=1 width=524)
                                  ->  Index Scan using sggeofacelist_pkey on sggeofacelist t3943863539361  (cost=0.29..8.31 rows=1 width=4974)
                                        Index Cond: (oid = t3943814704643.att_1114)
                            ->  Index Scan using sggeopointlist_idx on sggeopointlist t3943875522916  (cost=0.27..0.84 rows=1 width=48)
                                  Index Cond: ((t3943863539361.feature && feature) AND (t3943863539361.feature && feature))
                                  Filter: _st_intersects(t3943863539361.feature, feature)
                      ->  Index Scan using sgclasstab_id68a1139_idx on sgclasstab_id68 t3943875447103  (cost=0.14..0.29 rows=1 width=8)
                            Index Cond: (att_1139 = t3943875522916.oid)
                            Filter: (sgclasstab_id68.oid = oid)

如果我没有误读这里的任何内容,Postgres 首先执行交集,然后排除所有不被 sgclasstab_id68 中的对象引用的不相关几何。

交换这两个操作会不会更高效,或者我是否在这个查询中做了一些事情来使这个选项不可用?如果没有,有没有办法强制 Postgres 重新考虑?

PostgreSQL 9.3,PostGIS 2.1.1 r12113。

提前致谢(对于难以阅读的查询,我们深表歉意,它是自动生成的)。

【问题讨论】:

  • 请在查询中为表使用别名。不可读。
  • 我认为你的意思是不要使用像 t3943863539361 这样的别名,不是吗:D
  • 您实际上并不需要 && 边界框检查,因为 ST_Intersects 暗示了它。如果你删除它,并将 t3943863539361 重命名为 poly 等,调试起来会更容易。但是,首先想到的是您的空间索引没有被使用,因为在解释输出中没有使用 进行索引扫描。
  • 顺便说一句,AND sgclasstab_id68.oid = t3943875447103.oid 将列与自身进行比较,可能是“框架”的错字? (产生一个笛卡尔积)
  • @JohnBarça 哦,你是对的。但如果没有别名,它仍然无法阅读。

标签: sql postgresql postgis


【解决方案1】:

[不是答案] 仅供参考:清理后的查询(希望我没有犯任何错误):

UPDATE sgclasstab_id68 dst
SET att_1496 = (
    SELECT cla.att_1115
    FROM sgclasstab_id67 cla 
    JOIN sggeofacelist fac ON cla.att_1114 = fac.oid AND fac.oid = cla.att_1114
    JOIN sggeopointlist pnt ON fac.feature && (pnt.feature) AND ST_Intersects(fac.feature, pnt.feature)
    JOIN sgclasstab_id68 cla2 ON cla2.att_1139 = pnt.oid
    WHERE 1=1
        AND dst.oid = cla2.oid
        -- AND cla2.oid = cla2.oid
    LIMIT 1 
    )
    ;

[回答] 似乎未加别名的 cla2 (classtab_id68) 引用是指 inner 查询,而不是外部 UPDATE 语句中的目标表。所以所有classtab_id68 都将更新为相同的值。此外,连接列也缺少 FK 约束/索引。


更新:重新考虑表引用JOIN sgclasstab_id68 cla2 是不必要的;它引用与目标行相同的行,因此查询可以进一步简化为:

UPDATE sgclasstab_id68 dst
SET att_1496 = (
    SELECT c67.att_1115 
    FROM sgclasstab_id67 c67 
    JOIN sggeofacelist fl ON c67.att_1114 = fl.oid AND fl.oid = c67.att_1114
    JOIN sggeopointlist pnt ON (fl.feature && pnt.feature) AND ST_Intersects(fl.feature, pnt.feature)
    WHERE dst.att_1139 = pnt.oid 
    LIMIT 1
    )
        ;

[但仍然需要适当的 FK/索引。]


附录:子查询中的LIMIT 1(没有排序依据)也是可疑的。至少,您会希望您的更新具有某种确定性。这个子查询只是从结果集中随机选择一个行(如果有多个)并将其分配给目标表。似乎不合逻辑;至少对我来说不是。


最后,您实际上并不需要 scalar(?) 子查询,而是可以使用普通的 UPDATE 语法代替(我还删除了边界框连接,ST_intersects() 连接已经暗示了这一点:

UPDATE sgclasstab_id68 dst
SET att_1496 = c67.att_1115 
FROM sgclasstab_id67 c67    
JOIN sggeofacelist fl ON c67.att_1114 = fl.oid AND fl.oid = c67.att_1114
JOIN sggeopointlist pnt ON ST_Intersects(fl.feature, pnt.feature)
WHERE dst.att_1139 = pnt.oid    
    ;

【讨论】:

  • 不,我用可读的别名替换了别名(在本例中为 cla2)。最后的连接将内部 classtab_id68 表与其自身连接,而不是与外部连接。
  • 谢谢,我可以确认这个查询在功能上是相同的(并且更具可读性),但我不太明白答案。删除别名 dst 不会改变我的功能。我相信之前的引用是正确的,因为它在其他数百个案例中都有效。
  • 谢谢乔普,我相信你做到了!我确实对您的上一个陈述做了一些小改动,但您发现了问题。一旦我能够在这里发布答案,我将提供详细的解决方案。附言。抱歉,我不能投票给你,但这至少需要 15 声望。
【解决方案2】:

NOT an answer, just an attempt to clarify the question with formatting (taken from joop)。这仍然做你想做的事吗?如果是,explain now 的输出是什么?

UPDATE sgclasstab_id68 dst
    SET att_1496 = (
       SELECT cla.att_1115
    FROM sgclasstab_id67 cla 
       JOIN sggeofacelist fac ON cla.att_1114 = fac.oid AND fac.oid = cla.att_1114        
       JOIN sgclasstab_id68 cla2 ON cla2.att_1139 = pnt.oid
    WHERE  dst.oid = cla2.oid
       AND ST_Intersects(fac.feature, pnt.feature)       
  );

【讨论】:

  • 我指的是代码下方的 ANSWER 部分。无论如何,我的声誉不允许我在接下来的 7 小时内发布除 cmets 之外的任何内容,但 EXPLAIN 看起来就像没有限制条款的前一个。顺便提一句。我添加了JOIN sggeopointlist pnt ON fac.feature && (pnt.feature),以引用点几何图形,可以吗?
  • && 是不必要的,它只是表示边界框比较,它作为 ST_Intersects 的一部分自动处理。这就是为什么我将 AND ST_Intersects(fac.feature, pnt.feature) 放在 where 中的原因,因为我认为它比将其作为连接子句更清楚。那有意义吗?为一个好问题 +1,但您可能需要另外一对才能做任何有用的事情。
  • @JohnBarça 仅供参考:我的第一次重写中有一个错字。 (这些别名要了我的命 ;-)
  • @joop,不开玩笑,这些 ORM 生成的查询读起来很糟糕。你更新你的答案了吗?
  • 是的,我在 cl1 和 cl2 之间犯了一个错误,IIRC。 UPDATE 下面的部分现在是正确的(我猜)为了确定:从原始开始并进行一些搜索和替换(和其他)转换。我的结论是可以完全删除 cl2 引用(对外部表的引用是打算)清洗,冲洗,重复...... ;-[
【解决方案3】:

考虑到 joop 的回答,我想出了以下清理和修改后的我的第一个语句的版本

UPDATE sgclasstab_id68 SET att_1496 = (
    SELECT sgclasstab_id67.att_1115 
    FROM sgclasstab_id67, sggeofacelist, sggeopointlist 
    WHERE ((sgclasstab_id67.att_1114=sggeofacelist.oid )) 
        AND ((sgclasstab_id68.att_1139=sggeopointlist.oid )) 
        AND (ST_Intersects(sggeofacelist.feature,(sggeopointlist.feature))) 
        AND (sggeofacelist.oid=sgclasstab_id67.att_1114)  
)

这会产生以下执行计划

Update on sgclasstab_id68  (cost=0.00..1074.13 rows=100 width=736)
  ->  Seq Scan on sgclasstab_id68  (cost=0.00..1074.13 rows=100 width=736)
        SubPlan 1
          ->  Nested Loop  (cost=0.55..10.18 rows=1 width=516)
                ->  Nested Loop  (cost=0.29..9.33 rows=1 width=5482)
                      ->  Seq Scan on sgclasstab_id67  (cost=0.00..1.01 rows=1 width=524)
                      ->  Index Scan using sggeofacelist_pkey on sggeofacelist  (cost=0.29..8.31 rows=1 width=4974)
                            Index Cond: (oid = sgclasstab_id67.att_1114)
                ->  Index Scan using sggeopointlist_idx on sggeopointlist  (cost=0.27..0.84 rows=1 width=40)
                      Index Cond: (sggeofacelist.feature && feature)
                      Filter: ((sgclasstab_id68.att_1139 = oid) AND _st_intersects(sggeofacelist.feature, feature))

结果似乎是相同的,第一次测试已经表明它快了几个数量级。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-02-06
    • 2012-05-21
    • 1970-01-01
    • 2012-06-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多