暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

PostGIS查询优化之空间索引IO与CPU放大

Spatial Data 2021-05-10
2049
一 前言
Spatial SQL查询较慢主要原因如下:
  • 建立空间索引的图形列通过表达式、函数、类型转换后导致索引失效。

  • 部分空间函数根本不会走空间索引。

  • 索引效率低,IO与CPU放大严重。

本文主要阐述第三点,空间索引效率很低导致的IO与CPU放大的原因和解决方案。

二 IO、CPU放大原因
《PostGIS查询优化之特殊的空间关系ST_Disjoint》一文中简单介绍了下空间索引的原理(案例中也曾有IO、CPU放大的现象及其解决办法),本节继续回顾下原理,加深下印象。

首先,每个图形(不论是点、线、还是面)都有一个外接矩形,如下图,空间索引实际是基于图形外接矩形建立的。

当查询的时候,用查询条件的图形的bbox去索引里扫描,看哪些图形的bbox与查询条件图形的bbox有交集并返回,这就是查询计划里的索引扫描,是bbox与bbox的计算,IO密集型。

最后,对索引扫描到的空间数据,使用其真实的图形与查询条件真实图形做比较,获取真正的图形空间关系,这就是查询计划里的Filter,是geom与geom的计算,CPU密集型。

一个典型的空间查询就是先扫描索引,再Filter过滤,示例如下

    explain select * from house  where 
    st_intersects(geom,
    st_geomfromtext('Polygon((118 32,118.5 33,119 32,118 32))',4326)
    );

    那么想象下这么一个场景,如下图,有全国的暴雨等值面和全国电力设备点数据,要求查询暴雨面覆盖到的电力设备点数据。可以看到暴雨面状数据非常零散稀碎,实际覆盖的点数量有限,但是,它的外界矩形(红框)却出奇的大,几乎覆盖到了全部的点数据。如果我们回顾一下空间索引的工作机制,首先根据红框去扫描站点,近似于全表扫描了,这个空间索引基本就没效果了,IO放大很严重,之后的Filter也会导致CPU放大严重,索引极其低效。

    三 解决方案

    类似上图的情况,就是实际查询图形不大,可bbox很大,导致IOCPU放大从而查询性能低下的情况,一般遵循一个原则“”,本文将根据实际案例做说明。

    场景说明:当前有近10万电力设备点分布于全国,要求统计暴雨面影响到的电力设备数量。暴雨面是MultiPolygon类型,上图可知,数据描述如下图:

      #电力设备测试数量
      select count(*) from dlsb;
      count
      -------
      99007
      (1 行记录)


      # 测试的暴雨数据
      select count(*) from rain;
      count
      -------
      1
      (1 行记录)
      # 暴雨图斑多义图形,由几百个Polygon子图形组成
      select St_GeometryType(geom) GeomType,
      ST_NumGeometries(geom) GeomCount from rain;
      geomtype | geomcount
      -----------------+-----------
      ST_MultiPolygon | 307
      (1 行记录)
       不优化的时候,需要42s完成查询。
         select count(a.*) from dlsb a,rain b 
        where st_intersects(a.geom,b.geom);

        count
        -------
        12814
        (1 行记录)


        Time: 41401.309 ms (00:41.401)
        优化一:MultiPolygon拆Polygon

        新建一个拆分表,并建立索引,把大图形拆成小图形,结果存储到该表:
        --新建一个新的表,存储拆分后的图形
        CREATE TABLE rain_single
        (
        gid serial primary key,
        geom geometry(Polygon,4326)
        );
        --建立索引
        CREATE INDEX rain_single_geom_idx
        ON rain_single USING gist
        (geom);
        --将拆分结果存入新的表中
        INSERT INTO public.rain_single(geom) SELECT
        ST_GeometryN(a.geom, n) As geom FROM rain a
        CROSS JOIN generate_series(1,400) n WHERE n <= ST_NumGeometries(a.geom);

        优化查询,从42s到6s:


        select count(a.*) from dlsb a,rain_single b where st_intersects(a.geom,b.geom);


        count

        -------
        12814
        (1 行记录)

        Time: 6088.242 ms (00:06.088)

        遗留问题:
        如下图,拆分后,仍然有个别小的Polygon很细碎,但是外接矩形很大,同样导致资源放大,性能变低。

        优化二:Polygon切割
        PostGIS中提供了ST_Subdivide方法,可以把一个图形切成很多非常小的图形,优化一遗留的问题,如果把那个bbox的面切掉,每个切割的小图形的bbox就很小了。
        新建一个切割表,存储切割后的图形:
        #生成切割测试数据

        SELECT row_number() OVER() As id,geom into rain_Subdivide

        from (select ST_SubDivide(geom,40) geom from rain_single) as f(geom);


        alter table rain_Subdivide alter column geom type

        geometry(Polygon,4326) using geom:: geometry(Polygon,4326);

        #建立索引

        create index rain_Subdivide_geom_idx on abc using gist(geom);

        切割后查询,从6s到70ms:

        select count(a.*) from dlsb a,rain_Subdivide b where

        st_intersects(a.geom,b.geom);


        count

        -------
        12814
        (1 行记录)
        时间:69.127 ms

        明白问题的原因和原理,再想办法遵循原理去解决,就方便多了,小小的“拆”字,就让性能优化了近千倍。因此当遇到空间查询不走索引,使用不走索引的函数,使用索引效率低下,就参考PostGIS查询优化专题,总有一个能让你有所启发。
        文章转载自Spatial Data,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

        评论