【问题标题】:Is there any way to make this postgres query faster?有没有办法让这个 postgres 查询更快?
【发布时间】:2021-02-24 06:36:27
【问题描述】:

所以我有这个查询;

SELECT
    *
FROM
    api_points
WHERE
    cat_level_5 = ANY(ARRAY['7223b580c20758c7','3e23543ccb9a2ef4','bbaec92cedb8a3bc',...])
    AND
    NOT cat_level_5 = ANY(ARRAY['8ccb9d2231a318e4'])

关于地理空间点的记录大约有 2~300 万条。类别是分层的,有 5 列命名为 cat_level_1cat_level_2cat_level_3cat_level_4cat_level_5

此查询指定了许多 5 级类别,大约需要 1-2 分钟才能完成。我从使用 IN 运算符切换到使用数组的 =

我可以更改类别结构或类别数据类型以加快索引速度。

顺便说一句,当我使用 btree(cat_level_1, cat_level_2, cat_level_3, cat_level_4, cat_level_5) 添加索引时,它并没有太大的区别。

这里是result of EXPLAIN(ANALYZE, BUFFERS)

编辑

我对此进行了 SQL 查询编辑,结果为this

EXPLAIN (ANALYZE, BUFFERS)
SELECT *
FROM (
    SELECT * FROM unnest(ARRAY['7223b580c20758c7','3e23543ccb9a2ef4','bbaec92cedb8a3bc',...]) cat_level_5
    WHERE cat_level_5 NOT IN ( SELECT * FROM unnest(ARRAY['8ccb9d2231a318e4']) )
) categories
JOIN api_point
USING (cat_level_5)

表结构由这个django模型定义;

class Point(models.Model):
    poi_id = models.TextField(primary_key=True)
    poi_name = models.TextField(default = "")
    poi_address = models.TextField(default = "")
    location = models.TextField(default = "")
    location_info = models.TextField(default = "")
    history = models.DateField(default = datetime.date.today)
    city_code = models.IntegerField()
    town_code = models.IntegerField()
    quarter_code = models.IntegerField()
    icon_url = models.TextField()

    cat_level_1 = models.TextField()
    cat_level_1_name = models.TextField()
    cat_level_2 = models.TextField()
    cat_level_2_name = models.TextField()
    cat_level_3 = models.TextField()
    cat_level_3_name = models.TextField()
    cat_level_4 = models.TextField()
    cat_level_4_name = models.TextField()
    cat_level_5 = models.TextField()
    cat_level_5_name = models.TextField()

    lat = models.FloatField()
    lon = models.FloatField()

    point = PointField(default = Point(0.0, 0.0))

    last_modified = models.DateTimeField(auto_now=True)
    created_at = models.DateTimeField(auto_now_add=True)

SQL表结构如下:

CREATE TABLE public.api_poi
(
    poi_id text COLLATE pg_catalog."default" NOT NULL,
    poi_name text COLLATE pg_catalog."default" NOT NULL,
    poi_address text COLLATE pg_catalog."default" NOT NULL,
    location text COLLATE pg_catalog."default" NOT NULL,
    location_info text COLLATE pg_catalog."default" NOT NULL,
    history date NOT NULL,
    city_code integer NOT NULL,
    town_code integer NOT NULL,
    quarter_code integer NOT NULL,
    icon_url text COLLATE pg_catalog."default" NOT NULL,
    lat double precision NOT NULL,
    lon double precision NOT NULL,
    last_modified timestamp with time zone NOT NULL,
    created_at timestamp with time zone NOT NULL,
    point geometry(Point,4326) NOT NULL,
    cat_level_1 text COLLATE pg_catalog."default" NOT NULL,
    cat_level_1_name text COLLATE pg_catalog."default" NOT NULL,
    cat_level_2 text COLLATE pg_catalog."default" NOT NULL,
    cat_level_2_name text COLLATE pg_catalog."default" NOT NULL,
    cat_level_3 text COLLATE pg_catalog."default" NOT NULL,
    cat_level_3_name text COLLATE pg_catalog."default" NOT NULL,
    cat_level_4 text COLLATE pg_catalog."default" NOT NULL,
    cat_level_4_name text COLLATE pg_catalog."default" NOT NULL,
    cat_level_5 text COLLATE pg_catalog."default" NOT NULL,
    cat_level_5_name text COLLATE pg_catalog."default" NOT NULL,
    CONSTRAINT api_poi_pkey PRIMARY KEY (poi_id)
)

TABLESPACE pg_default;

ALTER TABLE public.api_poi
    OWNER to postgres;
-- Index: category_idx

-- DROP INDEX public.category_idx;

CREATE INDEX category_idx
    ON public.api_poi USING btree
    (cat_level_1 COLLATE pg_catalog."default" ASC NULLS LAST, cat_level_2 COLLATE pg_catalog."default" ASC NULLS LAST, cat_level_3 COLLATE pg_catalog."default" ASC NULLS LAST, cat_level_4 COLLATE pg_catalog."default" ASC NULLS LAST, cat_level_5 COLLATE pg_catalog."default" ASC NULLS LAST)
    TABLESPACE pg_default;
-- Index: point_namex

-- DROP INDEX public.point_namex;

CREATE INDEX point_idx
    ON public.api_poi USING brin
    (point)
    TABLESPACE pg_default;

【问题讨论】:

  • 如果没有EXPLAIN (ANALYZE, BUFFERS) 输出,很难给出一个有根据的答案。您是否查看过 ltree 扩展及其索引支持来为您的层次结构建模?
  • 抱歉,我会尽快添加输出,我添加了 ltree 扩展,但我不确定它是否会有所作为;如果我只使用第 5 级进行搜索,那么它在那个状态下是分层的吗?
  • 你好,我添加了EXPLAIN(ANALYZE, BUFFERS)的结果。
  • 什么工具以这种方式格式化解释分析输出并删除行估计?无论如何,这个查询看起来返回了大量的行(1962475),并且“过滤器删除的行数”暗示它选择了 88% 的表。
  • 这看起来不像是 EXPLAIN ANALYZE,它没有显示执行此查询所花费的时间。除了也缺少的其他信息。

标签: django postgresql query-optimization


【解决方案1】:

示例表 foo 有一个文本列 x,其中包含数字。

explain analyze select * from foo where x =any('{1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99}'::TEXT[]);

 Gather  (cost=1000.00..62144.07 rows=99 width=10) (actual time=0.682..364.252 rows=99 loops=1)
   Workers Planned: 2
   Workers Launched: 2
   ->  Parallel Seq Scan on foo  (cost=0.00..61134.17 rows=41 width=10) (actual time=237.705..358.203 rows=33 loops=3)
         Filter: (x = ANY ('{1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99}'::text[]))
         Rows Removed by Filter: 333300
 Planning Time: 0.219 ms
 Execution Time: 364.292 ms

注意 =ANY 运算符在数组中使用线性搜索,如果数组很长,则速度很慢。

explain analyze select * from (select * from unnest('{1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99}'::TEXT[]) x 
where x not in ('7','8')) a join foo using (x);
                                                           QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------
 Gather  (cost=1002.45..12146.72 rows=97 width=36) (actual time=0.904..44.894 rows=97 loops=1)
   Workers Planned: 2
   Workers Launched: 2
   ->  Hash Join  (cost=2.45..11137.02 rows=40 width=36) (actual time=24.904..38.844 rows=32 loops=3)
         Hash Cond: (foo.x = x.x)
         ->  Parallel Seq Scan on foo  (cost=0.00..9571.67 rows=416667 width=10) (actual time=0.007..16.592 rows=333333 loops=3)
         ->  Hash  (cost=1.24..1.24 rows=97 width=32) (actual time=0.057..0.057 rows=97 loops=3)
               Buckets: 1024  Batches: 1  Memory Usage: 12kB
               ->  Function Scan on unnest x  (cost=0.00..1.24 rows=97 width=32) (actual time=0.020..0.040 rows=97 loops=3)
                     Filter: (x <> ALL ('{7,8}'::text[]))
                     Rows Removed by Filter: 2

使用 unnest 会产生更快的哈希连接,从而避免通过数组进行线性搜索。此外,我在生成要获取的值列表的子查询中添加了一个带有“否定列表以排除特定用户的黑名单类别”的“NOT IN”子句,因此将列入黑名单的值从列表中删除一次开始。

因此,它会生成您想要的值列表,删除列入黑名单的值,对其进行哈希处理,然后扫描表,查找哈希中该列的每一行的值。

这应该会使查询更快一些,但它不能解决读取 3GB 数据的主要问题。

当我使用 btree(cat_level_1, cat_level_2, cat_level_3, cat_level_4, cat_level_5) 添加索引时,顺便说一句,它并没有太大的区别。

这是正常的,你没有在查询中使用 cat_levels_1 到 4,所以它不能使用位于索引列列表末尾的 cat_level_5。也许 cat_level_5 上的索引会有所帮助,但我对此表示怀疑,因为此查询选择了表的很大一部分。

【讨论】:

  • 感谢您的精彩回答,我添加了许多其他详细信息和查询结果,调整了您的解决方案,有一个提升,但我想我们因为 3GB 数据而被卡住了。我需要所有这些来进一步过滤和处理一堆其他细节,所以我们不能削减它。
  • 将所有数据传输到 python 需要一段时间,并且在那边也需要大量的 RAM...
  • 可悲的是,是的...但这不是什么大问题,因为我不直接将数据提取到 python 对象中,而是在连续的提取上下文中处理它们,因此它基本上检索数据之一一次并在检索它的同时做一堆其他的事情,这个查询是目前最耗时的操作。
  • 这是我们可以对这个查询做的最大可能的增强吗? ltree 会更好地影响它吗?在 django 中实现数据插入语句真的很困难,但如果值得的话,它可能是。如果没有更多的性能改进,那么我将检查它是否已解决,因为它有助于提高性能。
  • 不,如果您选择表中的一小部分行,索引会有所帮助,但这里不是这种情况。
猜你喜欢
  • 1970-01-01
  • 2022-11-03
  • 1970-01-01
  • 1970-01-01
  • 2020-07-29
  • 2022-01-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多