为什么PostgreSQL不使用索引进行此连接查询

为什么PostgreSQL不使用索引进行此连接查询,postgresql,Postgresql,生成以下查询计划: explain (analyze) select event_user_detail.* from event_user_detail inner join guest_list on event_user_detail.guest_list_id = guest_list.id where guest_list.event_id=2985739029 即使guest\u listevent\u id上有一个索引。有人能解释为什么会发生这种情况,以及是否

生成以下查询计划:

explain (analyze) select
    event_user_detail.*

from event_user_detail
inner join guest_list on event_user_detail.guest_list_id = guest_list.id

where
    guest_list.event_id=2985739029
即使guest\u listevent\u id上有一个索引。有人能解释为什么会发生这种情况,以及是否有办法解决它吗

如果我将其分为两个查询,其中一个查询只是获取guest_列表ID,然后执行一个简单的in…ID,那么查询速度会非常快。我试着用一个子查询做同样的事情,但我认为乐观主义者把它变成了一个连接

Gather  (cost=1052.56..43408.58 rows=244 width=97) (actual time=66.570..67.810 rows=0 loops=1)
  Workers Planned: 2
  Workers Launched: 2
  ->  Nested Loop  (cost=52.56..42384.18 rows=102 width=97) (actual time=57.183..57.183 rows=0 loops=3)
        ->  Parallel Seq Scan on guest_list  (cost=0.00..13151.33 rows=5 width=8) (actual time=56.941..57.169 rows=2 loops=3)
              Filter: (event_id = '2985739029'::bigint)
              Rows Removed by Filter: 254489
        ->  Bitmap Heap Scan on event_user_detail  (cost=52.56..5830.93 rows=1564 width=97) (actual time=0.007..0.007 rows=0 loops=5)
              Recheck Cond: (guest_list_id = guest_list.id)
              ->  Bitmap Index Scan on idx_event_user_detail_guest_list_id  (cost=0.00..52.17 rows=1564 width=0) (actual time=0.005..0.005 rows=0 loops=5)
                    Index Cond: (guest_list_id = guest_list.id)
Planning time: 0.252 ms
Execution time: 67.838 ms
您可以使用CTE执行以下操作:

以客人为中心 从来宾列表中选择id,其中事件id=2985739029限制1 选择*从事件\用户\详细信息,其中来宾\列表\从来宾中选择id 旧版本postgresql中的CTE在一个事务中像单独的查询一样运行,并独立计划,但不会将结果从CTE发送到客户端

你可以在报纸上读到他们。请注意,自12版postgres以来,这种行为发生了变化,如果您想保留旧版本,您应该这样写:

以客为物 从来宾列表中选择id,其中事件id=2985739029限制1 选择*从事件\用户\详细信息,其中来宾\列表\从来宾中选择id 此外,它们对于避免更新中的死锁非常有用:

-- ----------------------------
-- Table structure for guest_list
-- ----------------------------
DROP TABLE IF EXISTS "public"."guest_list";
CREATE TABLE "public"."guest_list" (
  "id" int8 NOT NULL,
  "creation_date" timestamp(6),
  "last_modification_date" timestamp(6),
  "uuid" uuid,
  "deleted" bool NOT NULL,
  "name" varchar(255) COLLATE "pg_catalog"."default",
  "version" int4,
  "event_id" int8,
  "permanent_guest_list_id" int8,
  "color" varchar(255) COLLATE "pg_catalog"."default"
)
;

-- ----------------------------
-- Indexes structure for table guest_list
-- ----------------------------
CREATE INDEX "idx_guest_list_event_id" ON "public"."guest_list" USING btree (
  "event_id" "pg_catalog"."int8_ops" ASC NULLS LAST
);
CREATE INDEX "idx_guest_list_permanent_guest_list_id" ON "public"."guest_list" USING btree (
  "permanent_guest_list_id" "pg_catalog"."int8_ops" ASC NULLS LAST
);

-- ----------------------------
-- Uniques structure for table guest_list
-- ----------------------------
ALTER TABLE "public"."guest_list" ADD CONSTRAINT "uk_o4sa0dw6lcdjv96gl2p96xwki" UNIQUE ("uuid");

-- ----------------------------
-- Primary Key structure for table guest_list
-- ----------------------------
ALTER TABLE "public"."guest_list" ADD CONSTRAINT "guest_list_pkey" PRIMARY KEY ("id");

-- ----------------------------
-- Foreign Keys structure for table guest_list
-- ----------------------------
ALTER TABLE "public"."guest_list" ADD CONSTRAINT "fk7tk6fxgyo4h7ykelb9c0pe5ap" FOREIGN KEY ("event_id") REFERENCES "public"."event" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION;
ALTER TABLE "public"."guest_list" ADD CONSTRAINT "guest_list_permanent_guest_list_id_fkey" FOREIGN KEY ("permanent_guest_list_id") REFERENCES "public"."permanent_guest_list" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION;

-- ----------------------------
-- Table structure for event_user_detail
-- ----------------------------
DROP TABLE IF EXISTS "public"."event_user_detail";
CREATE TABLE "public"."event_user_detail" (
  "id" int8 NOT NULL,
  "creation_date" timestamp(6),
  "last_modification_date" timestamp(6),
  "uuid" uuid,
  "deleted" bool NOT NULL,
  "name" varchar(255) COLLATE "pg_catalog"."default",
  "value" text COLLATE "pg_catalog"."default",
  "version" int4,
  "event_id" int8,
  "user_id" int8,
  "guest_list_id" int8,
  "reference_user_id" int8
)
;

-- ----------------------------
-- Indexes structure for table event_user_detail
-- ----------------------------
CREATE INDEX "idx_event_user_detail_deleted" ON "public"."event_user_detail" USING btree (
  "deleted" "pg_catalog"."bool_ops" ASC NULLS LAST
);
CREATE INDEX "idx_event_user_detail_event_id" ON "public"."event_user_detail" USING btree (
  "event_id" "pg_catalog"."int8_ops" ASC NULLS LAST
);
CREATE INDEX "idx_event_user_detail_guest_list_id" ON "public"."event_user_detail" USING btree (
  "guest_list_id" "pg_catalog"."int8_ops" ASC NULLS LAST
);
CREATE INDEX "idx_event_user_detail_user_id" ON "public"."event_user_detail" USING btree (
  "user_id" "pg_catalog"."int8_ops" ASC NULLS LAST
);

-- ----------------------------
-- Uniques structure for table event_user_detail
-- ----------------------------
ALTER TABLE "public"."event_user_detail" ADD CONSTRAINT "uk_orfh8fkwtk681af38a65everr" UNIQUE ("uuid");

-- ----------------------------
-- Primary Key structure for table event_user_detail
-- ----------------------------
ALTER TABLE "public"."event_user_detail" ADD CONSTRAINT "event_user_detail_pkey" PRIMARY KEY ("id");

-- ----------------------------
-- Foreign Keys structure for table event_user_detail
-- ----------------------------
ALTER TABLE "public"."event_user_detail" ADD CONSTRAINT "fk8bffonom9l1fgcanegl9nm641" FOREIGN KEY ("user_id") REFERENCES "public"."user" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION;
ALTER TABLE "public"."event_user_detail" ADD CONSTRAINT "fk_event_user_detail_guest_list_id" FOREIGN KEY ("guest_list_id") REFERENCES "public"."guest_list" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION;
ALTER TABLE "public"."event_user_detail" ADD CONSTRAINT "fk_event_user_detail_reference_user_id" FOREIGN KEY ("reference_user_id") REFERENCES "public"."user" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION;
ALTER TABLE "public"."event_user_detail" ADD CONSTRAINT "fkisr2ccpapw537ntw4c0mlytcw" FOREIGN KEY ("event_id") REFERENCES "public"."event" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION;

这将使所有行按特定顺序锁定,从而防止普通更新查询可能出现的死锁,例如,两个查询都需要修改ID 1和ID 2,有了这个CTE,就不可能有第一个锁1和等待2,而第二个锁2和等待1。

它大大高估了它将为来宾列表中的每一行找到的事件用户详细信息中的行数,可能是因为来宾列表中有一些行在事件用户详细信息中有很多条目,只是不是你在这里选择的那些。它认为将要找到的大量行使并行查询看起来很有吸引力,但获得并行查询的方法是使用guest_列表上的seq scan。这就是它的作用


通过将每个聚集的最大并行工作人员数设置为0,可以禁用并行查询。如果您并没有从并行查询中获得太多好处,这可能是一个很好的解决方案。如果你确实从中受益,但又不想禁用它,那么你至少可以在当前课程中这样做,看看我的理论是否正确。

我同意jjanes的答案,但我想建议进行以下附加实验:

尝试分析事件\用户\详细信息;看看这是否能改善估计

这可能是因为随机页面成本太高:它是为旋转磁盘设计的,估计索引扫描相对昂贵。如果降低该参数,PostgreSQL将更容易使用索引扫描


我还没有仔细考虑解释计划,所以没有添加实际答案,但看起来您的查询涉及sql-92笛卡尔连接和连接的老式where子句。我怀疑您想加入guest\u list\u id=id,为什么要使用交叉连接,然后在WHERE子句中隐式地使其成为内部连接?只是做一个内部连接。这是我的ORM-但实际上它在查询计划中没有任何区别。现在更新了问题。如果使用来宾列表内部加入来宾列表上的事件用户详细信息。事件id=2985739029和事件用户详细信息。来宾列表id=来宾列表id=来宾列表id,其中来宾列表。事件id=2985739029会发生什么情况?另外,如果两个请求运行速度很快,您可以这样做:使用guets作为从来宾列表中选择id,其中event\u id=xxx LIMIT 1选择*从event\u user\u detail中选择id,其中来宾列表中选择id。它应该工作得很快。也许你需要一个新版本的物化版本。谢谢。这是目前为止最好的解决方案,但奇怪的是,在一个简单的连接上需要这样做?@Piotr我不知道更好的方法,并且在planner生成次优计划时总是使用这种方法。@a_horse_和_no_name你是对的,我修正了。显然,你可以使用黑客迫使PostgreSQL不内联:设置max_parallel_workers_per_gather=0;修好了。哇!有什么办法可以让我弄清楚为什么在这么简单的查询中会出现这种情况吗?我推测guest\u list中的一些条目在event\u user\u细节中确实有大量条目。这是真的吗?如果是的话,这就是问题的答案。如果没有,那么你的统计数据可能会有很大的偏差,也许你可以用真空分析来修正它们。不幸的是,分析没有帮助,但这让我觉得也许我应该亲自检查这些分析,并了解规划者为什么会这样做。另外,这是AWS RDS Aurora,所以我希望他们已经将这些变量设置为适合其环境的适当值?事实证明,在AWS Aurora!上,random_page_cost设置为4!。将其设置为1.5或更低可修复此问题。奇怪的
WITH to_update AS (
    SELECT * FROM my_table WHERE condition 
    ORDER BY id ASC FOR UPDATE
)
UPDATE my_table SET ... WHERE condition;