对于一个特定的查询,我有以下两个查询计划(第二个是通过关闭seqscan获得的):

seqscan is on

seqscan is off

第二个计划的成本估算低于第一个计划,但是,pg仅在被迫 Select 第二个计划的情况下(通过关闭seqscan) Select 第二个计划.

是什么导致了这种行为?


编辑:用 comments 中要求的信息更新问题:

查询1的EXPLAIN (ANALYZE, BUFFERS, VERBOSE)输出(seqscan打开;不使用索引).也可在https://explain.depesz.com/s/cGLY处查看:

QUERY PLAN
Limit  (cost=2449.76..840962.24 rows=1 width=87) (actual time=25701.021..26540.060 rows=10 loops=1)
  Output: books.id, books.title, books.authors, books.meta
  Buffers: shared hit=2254959
  ->  Nested Loop Left Join  (cost=2449.76..840962.24 rows=1 width=87) (actual time=25289.899..26128.923 rows=10 loops=1)
        Output: books.id, books.title, books.authors, books.meta
        Join Filter: (photos."bookId" = books.id)
        Rows Removed by Join Filter: 62876457
        Filter: (photos.id IS NULL)
        Rows Removed by Filter: 707
        Buffers: shared hit=2254959
        ->  Gather  (cost=2449.76..835403.18 rows=1 width=87) (actual time=391.874..494.669 rows=658 loops=1)
              Output: books.id, books.title, books.authors, books.meta
              Workers Planned: 2
              Workers Launched: 2
              Buffers: shared hit=11837
              ->  Parallel Bitmap Heap Scan on public.books  (cost=1449.76..834403.08 rows=1 width=87) (actual time=868.495..874.706 rows=554 loops=3)
                    Output: books.id, books.title, books.authors, books.meta
                    Recheck Cond: ((books.meta !~~ 'foo%'::text) AND (books.meta <> 'bar'::text))
                    Filter: ((books.meta ~~ 'baz%'::text) AND (books.id <> ALL ('{19643405,19702275,19784617,28454289,28491188,28491190,28491205,28521585,28521596,28521627,28521638,28521649,28521658,28521678,28521680,28521689,28521700,28518165,28515245,28515256,28515288,28515299,28515310,28515342,28515353,28515364,28515407,28515736,28518100,28518219,28518273,28518370,28518424,28518478,28518489}'::integer[])))
                    Rows Removed by Filter: 77897
                    Heap Blocks: exact=11567
                    Buffers: shared hit=11837
                    Worker 0:  actual time=1107.154..1115.320 rows=1113 loops=1
                      JIT:
                        Functions: 6
                        Options: Inlining true, Optimization true, Expressions true, Deforming true
                        Timing: Generation 5.001 ms, Inlining 471.271 ms, Optimization 365.866 ms, Emission 269.821 ms, Total 1111.959 ms
                      Buffers: shared hit=40
                    Worker 1:  actual time=1108.335..1108.975 rows=541 loops=1
                      JIT:
                        Functions: 6
                        Options: Inlining true, Optimization true, Expressions true, Deforming true
                        Timing: Generation 11.915 ms, Inlining 450.341 ms, Optimization 364.168 ms, Emission 293.461 ms, Total 1119.885 ms
                      Buffers: shared hit=21
                    ->  Bitmap Index Scan on books_meta_partial_exclude_foo_and_bar  (cost=0.00..1449.76 rows=2194002 width=0) (actual time=41.801..41.802 rows=238689 loops=1)
                          Buffers: shared hit=209
        ->  Seq Scan on public.photos  (cost=0.00..4364.58 rows=95558 width=8) (actual time=0.002..17.127 rows=95558 loops=658)
              Output: photos.id, photos.url, photos.type, photos."userId", photos."libraryId", photos."bookId", photos."libraryBookId", photos."isPrimaryPic", photos."processingStatus", photos."createdAt", photos."updatedAt", photos."otherData"
              Buffers: shared hit=2243122
Planning:
  Buffers: shared hit=17
Planning Time: 0.758 ms
JIT:
  Functions: 24
  Options: Inlining true, Optimization true, Expressions true, Deforming true
  Timing: Generation 20.953 ms, Inlining 1005.367 ms, Optimization 915.620 ms, Emission 705.338 ms, Total 2647.278 ms
Execution Time: 26544.310 ms

查询2的EXPLAIN (ANALYZE, BUFFERS, VERBOSE)输出(seqscan off;使用索引).也可在https://explain.depesz.com/s/VDfP处查看:

QUERY PLAN
Limit  (cost=2450.18..835405.63 rows=1 width=87) (actual time=1110.719..2424.086 rows=10 loops=1)
  Output: books.id, books.title, books.authors, books.meta
  Buffers: shared hit=16834
  ->  Nested Loop Left Join  (cost=2450.18..835405.63 rows=1 width=87) (actual time=464.812..1778.175 rows=10 loops=1)
        Output: books.id, books.title, books.authors, books.meta
        Filter: (photos.id IS NULL)
        Rows Removed by Filter: 1321
        Buffers: shared hit=16834
        ->  Gather  (cost=2449.76..835403.18 rows=1 width=87) (actual time=411.878..1753.914 rows=1232 loops=1)
              Output: books.id, books.title, books.authors, books.meta
              Workers Planned: 2
              Workers Launched: 2
              Buffers: shared hit=11822
              ->  Parallel Bitmap Heap Scan on public.books  (cost=1449.76..834403.08 rows=1 width=87) (actual time=653.691..663.053 rows=411 loops=3)
                    Output: books.id, books.title, books.authors, books.meta
                    Recheck Cond: ((books.meta !~~ 'foo%'::text) AND (books.meta <> 'bar'::text))
                    Filter: ((books.meta ~~ 'baz%'::text) AND (books.id <> ALL ('{19643405,19702275,19784617,28454289,28491188,28491190,28491205,28521585,28521596,28521627,28521638,28521649,28521658,28521678,28521680,28521689,28521700,28518165,28515245,28515256,28515288,28515299,28515310,28515342,28515353,28515364,28515407,28515736,28518100,28518219,28518273,28518370,28518424,28518478,28518489}'::integer[])))
                    Rows Removed by Filter: 77893
                    Heap Blocks: exact=11611
                    Buffers: shared hit=11822
                    Worker 0:  actual time=774.890..774.891 rows=1 loops=1
                      JIT:
                        Functions: 6
                        Options: Inlining true, Optimization true, Expressions true, Deforming true
                        Timing: Generation 14.889 ms, Inlining 364.167 ms, Optimization 205.348 ms, Emission 205.226 ms, Total 789.630 ms
                      Buffers: shared hit=1
                    Worker 1:  actual time=780.309..780.311 rows=1 loops=1
                      JIT:
                        Functions: 6
                        Options: Inlining true, Optimization true, Expressions true, Deforming true
                        Timing: Generation 4.595 ms, Inlining 362.465 ms, Optimization 209.509 ms, Emission 208.145 ms, Total 784.715 ms
                      Buffers: shared hit=1
                    ->  Bitmap Index Scan on books_meta_partial_exclude_foo_and_bar  (cost=0.00..1449.76 rows=2194002 width=0) (actual time=56.500..56.501 rows=238689 loops=1)
                          Buffers: shared hit=209
        ->  Index Scan using "photos_bookId_idx" on public.photos  (cost=0.42..2.44 rows=1 width=8) (actual time=0.012..0.013 rows=1 loops=1232)
              Output: photos.id, photos.url, photos.type, photos."userId", photos."libraryId", photos."bookId", photos."libraryBookId", photos."isPrimaryPic", photos."processingStatus", photos."createdAt", photos."updatedAt", photos."otherData"
              Index Cond: (photos."bookId" = books.id)
              Buffers: shared hit=5012
Planning:
  Buffers: shared hit=17
Planning Time: 2.640 ms
JIT:
  Functions: 25
  Options: Inlining true, Optimization true, Expressions true, Deforming true
  Timing: Generation 39.565 ms, Inlining 839.818 ms, Optimization 765.817 ms, Emission 599.027 ms, Total 2244.228 ms
Execution Time: 2455.226 ms

编辑2:添加关于表 struct 、索引和查询本身的信息

-- Table: public.books

-- DROP TABLE IF EXISTS public.books;

CREATE TABLE IF NOT EXISTS public.books
(
    id integer NOT NULL DEFAULT nextval('books_id_seq'::regclass),
    title text COLLATE pg_catalog."default" NOT NULL,
    authors text COLLATE pg_catalog."default" NOT NULL,
    slug text COLLATE pg_catalog."default" NOT NULL,
    "desc" text COLLATE pg_catalog."default",
    meta text COLLATE pg_catalog."default",
    "createdAt" timestamp(3) without time zone NOT NULL DEFAULT CURRENT_TIMESTAMP,
    "updatedAt" timestamp(3) without time zone NOT NULL,
    tsv tsvector GENERATED ALWAYS AS (to_tsvector('english'::regconfig, ((COALESCE(title, ''::text) || ' '::text) || COALESCE(authors, ''::text)))) STORED,
    CONSTRAINT books_pkey PRIMARY KEY (id)
)

TABLESPACE pg_default;

ALTER TABLE IF EXISTS public.books
    OWNER to [REDACTED];
-- Index: books_fts_idx

-- DROP INDEX IF EXISTS public.books_fts_idx;

CREATE INDEX IF NOT EXISTS books_fts_idx
    ON public.books USING gin
    (tsv)
    TABLESPACE pg_default;
-- Index: books_meta_partial_exclude_foo_and_bar

-- DROP INDEX IF EXISTS public.books_meta_partial_exclude_foo_and_bar;

CREATE INDEX IF NOT EXISTS books_meta_partial_exclude_foo_and_bar
    ON public.books USING btree
    (meta COLLATE pg_catalog."default" ASC NULLS LAST)
    TABLESPACE pg_default
    WHERE meta !~~ 'foo%'::text AND meta <> 'bar'::text;
-- Index: books_slug_key

-- DROP INDEX IF EXISTS public.books_slug_key;

CREATE UNIQUE INDEX IF NOT EXISTS books_slug_key
    ON public.books USING btree
    (slug COLLATE pg_catalog."default" ASC NULLS LAST)
    TABLESPACE pg_default;
-- Table: public.photos

-- DROP TABLE IF EXISTS public.photos;

CREATE TABLE IF NOT EXISTS public.photos
(
    id integer NOT NULL DEFAULT nextval('photos_id_seq'::regclass),
    url text COLLATE pg_catalog."default" NOT NULL,
    type text COLLATE pg_catalog."default",
    "userId" integer,
    "libraryId" integer,
    "bookId" integer,
    "libraryBookId" integer,
    "isPrimaryPic" boolean DEFAULT false,
    "processingStatus" "PhotoProcessingStatus" NOT NULL DEFAULT 'UNPROCESSED'::"PhotoProcessingStatus",
    "createdAt" timestamp(3) without time zone NOT NULL DEFAULT CURRENT_TIMESTAMP,
    "updatedAt" timestamp(3) without time zone NOT NULL,
    "otherData" jsonb,
    CONSTRAINT photos_pkey PRIMARY KEY (id),
    CONSTRAINT "photos_bookId_fkey" FOREIGN KEY ("bookId")
        REFERENCES public.books (id) MATCH SIMPLE
        ON UPDATE CASCADE
        ON DELETE SET NULL,
    CONSTRAINT "photos_libraryBookId_fkey" FOREIGN KEY ("libraryBookId")
        REFERENCES public.library_books (id) MATCH SIMPLE
        ON UPDATE CASCADE
        ON DELETE SET NULL,
    CONSTRAINT "photos_libraryId_fkey" FOREIGN KEY ("libraryId")
        REFERENCES public.libraries (id) MATCH SIMPLE
        ON UPDATE CASCADE
        ON DELETE SET NULL,
    CONSTRAINT "photos_userId_fkey" FOREIGN KEY ("userId")
        REFERENCES public.users (id) MATCH SIMPLE
        ON UPDATE CASCADE
        ON DELETE SET NULL
)

TABLESPACE pg_default;

ALTER TABLE IF EXISTS public.photos
    OWNER to [REDACTED];
-- Index: photos_bookId_idx

-- DROP INDEX IF EXISTS public."photos_bookId_idx";

CREATE INDEX IF NOT EXISTS "photos_bookId_idx"
    ON public.photos USING btree
    ("bookId" ASC NULLS LAST)
    TABLESPACE pg_default;
-- Index: photos_libraryId_idx

-- DROP INDEX IF EXISTS public."photos_libraryId_idx";

CREATE INDEX IF NOT EXISTS "photos_libraryId_idx"
    ON public.photos USING btree
    ("libraryId" ASC NULLS LAST)
    TABLESPACE pg_default;
-- Index: photos_userId_idx

-- DROP INDEX IF EXISTS public."photos_userId_idx";

CREATE INDEX IF NOT EXISTS "photos_userId_idx"
    ON public.photos USING btree
    ("userId" ASC NULLS LAST)
    TABLESPACE pg_default;

查询本身是:

SELECT
  books.id, books.title, books.authors, books.meta
FROM books
LEFT JOIN photos ON photos."bookId" = books.id
WHERE photos.id IS NULL
AND books.id NOT IN (19643405,19702275,19784617,28454289,28491188,28491190,28491205,28521585,28521596,28521627,28521638,28521649,28521658,28521678,28521680,28521689,28521700,28518165,28515245,28515256,28515288,28515299,28515310,28515342,28515353,28515364,28515407,28515736,28518100,28518219,28518273,28518370,28518424,28518478,28518489)
AND meta NOT LIKE 'foo%'
AND meta != 'bar'
AND meta LIKE 'baz%'
LIMIT 10; 

推荐答案

这两个计划实际上是并列的,它们的预期成本相差不到1%.规划者避免充分充实那些显然是Bundle 在一起的计划,以避免额外的工作.

请参见源代码中的模糊比较路径成本.

Postgresql相关问答推荐

用于JSON数组的带有组合条件的Postgres JSONB Select 查询

如何在PostgreSQL中对深度嵌套的jsonb属性创建索引

将列类型从文本[]更改为jsonb[]

将CSV文件复制到临时表

安装age-installation时出错

Postgres 视图定义 (pgAdmin) 被删除了 comments ?

如何创建一个触发器来传播对主键表的更新?

在 jOOQ 中使用 $$ 引用字符串

PostgreSQL - 列出模式中的所有唯一约束

在 jOOQ 中 Select 相同的命名列

Java - 日期保存为前一天

Heroku PGError:operator does not exist: character varying = integer

如何向文本字段添加长度约束

返回 NULL 的空数组的 array_length()

SQL - 提示引用列

psql 致命角色不存在

如何更改 PostgreSQL 中的日期样式?

避免通过 JPA 将null值插入数据库表

没有函数匹配给定的名称和参数类型

在 pg_restore 期间排除表