Oli*_*son 3 postgresql optimization execution-plan subquery explain
我有以下查询:
SELECT id,
email,
first_name as "firstName",
last_name as "lastName",
is_active as "isActive",
password,
access,
CASE
WHEN access < 3 THEN (
SELECT
CASE WHEN count(*) = 1 THEN true ELSE false END
FROM user_rating_entity ure
WHERE ure.user_id = u.id
AND ure.rating_entity_id = :re_id
)
ELSE true
END as "isResponsible"
FROM users u
WHERE u.id = :id
Run Code Online (Sandbox Code Playgroud)
如果access > 3
,字段“isResponsible”应直接设置为true,并且不应执行子查询。我在这两种情况下都使用了解释分析,其中 access>=
和<
to 3但我得到了相同的输出。
为什么呢?
这里有阅读查询计划的三个重要部分,
您没有提供任何示例数据,所以让我们创建一些。
CREATE TABLE foo AS
SELECT x FROM generate_series(1,100) AS x;
Run Code Online (Sandbox Code Playgroud)
而且,现在让我们在可能的执行范围之外运行带有子查询的基本查询。
EXPLAIN ANALYZE
SELECT
x,
(CASE WHEN x>200 THEN (SELECT sum(x) FROM foo) END)
FROM foo;
Run Code Online (Sandbox Code Playgroud)
该计划将表明该案件已被执行,但从未被执行。
Seq Scan on foo (cost=2.26..4.51 rows=100 width=4) (actual time=0.017..0.047 rows=100 loops=1)
InitPlan 1 (returns $0)
-> Aggregate (cost=2.25..2.26 rows=1 width=4) (never executed)
-> Seq Scan on foo foo_1 (cost=0.00..2.00 rows=100 width=4) (never executed)
Planning time: 0.101 ms
Execution time: 0.118 ms
(6 rows)
Run Code Online (Sandbox Code Playgroud)
你可以看到 with (never execution)就Aggregate
行了。但是,如果我们将其设置为类似的内容,CASE WHEN x>20 THEN (SELECT sum(x) FROM foo
您会看到更多
Seq Scan on foo (cost=2.26..4.51 rows=100 width=4) (actual time=0.020..0.095 rows=100 loops=1)
InitPlan 1 (returns $0)
-> Aggregate (cost=2.25..2.26 rows=1 width=4) (actual time=0.043..0.043 rows=1 loops=1)
-> Seq Scan on foo foo_1 (cost=0.00..2.00 rows=100 width=4) (actual time=0.006..0.019 rows=100 loops=1)
Planning time: 0.092 ms
Execution time: 0.158 ms
(6 rows)
Run Code Online (Sandbox Code Playgroud)
在这里我们可以看到 Aggregate 通过loops=1
时间循环。PostgreSQL 意识到它不是相关的子查询,它只是将其简化为文字(本质上)。现在让我们确保它是相关的。
EXPLAIN ANALYZE
SELECT
x,
(CASE WHEN x>20 THEN (SELECT sum(f2.x)+f1.x FROM foo AS f2) END)
FROM foo AS f1;
Run Code Online (Sandbox Code Playgroud)
现在你会看到这个计划
Seq Scan on foo f1 (cost=0.00..228.50 rows=100 width=4) (actual time=0.020..3.210 rows=100 loops=1)
SubPlan 1
-> Aggregate (cost=2.25..2.26 rows=1 width=4) (actual time=0.038..0.038 rows=1 loops=80)
-> Seq Scan on foo f2 (cost=0.00..2.00 rows=100 width=4) (actual time=0.005..0.017 rows=100 loops=80)
Planning time: 0.104 ms
Execution time: 3.272 ms
Run Code Online (Sandbox Code Playgroud)
这里的关键是聚合loops=80
本身需要loops=80
seq 扫描。
这都是一般性的,但如果没有您的示例数据或查询计划,我只能给出这些。
埃文已经指出,你可能忽略了(never executed)
在输出EXPLAIN ANALYZE
。
在现代 Postgres 中编写查询的更简洁、更通用的方法是使用LATERAL
子查询(不一定更快):
SELECT id, email
, first_name AS "firstName"
, last_name AS "lastName"
, is_active AS "isActive"
, password, access
, COALESCE(ure.resp, true) AS "isResponsible"
FROM users u
LEFT JOIN LATERAL (
SELECT (count(*) = 1) AS resp
FROM user_rating_entity
WHERE user_id = u.id -- lateral reference
AND rating_entity_id = :re_id
) ure ON u.access < 3
WHERE u.id = :id;
Run Code Online (Sandbox Code Playgroud)
就像我评论的那样,COALESCE()
是您特定CASE
表达的更优雅的替代品。
但你说count(*)
是从来没有null
?那为什么COALESCE()
?
即使它count(*)
本身从不null
(0 表示“无行”),它LEFT JOIN
仍然会null
在不满足连接条件的情况下产生。这就是这里的重点:Postgres 不计算u.access < 3
不满足连接条件的外部行。我们根据您的原始查询将其null
折叠为true
。
正如我们在看到埃文的回答,count(*)
触发顺序扫描的user_rating_entity
在每一行users
有资格。对于小表或很少的用户来说还可以,但对于大表来说是有问题的。
使用匹配索引以允许索引扫描可以显着更快:
CREATE INDEX foo ON user_rating_entity (user_id, rating_entity_id)
Run Code Online (Sandbox Code Playgroud)
如果每个计数的行数超过几行,则有更快的查询技术。但这扩大了这个问题的范围......
有关的:
或者:
SELECT ...
, CASE WHEN ure.ct <> 1 THEN false ELSE true END AS "isResponsible"
-- COALESCE(NOT ure.ct <> 1, true) -- equivalent
-- ure.ct = 1 -- NOT equivalent, misses NULL case
FROM users u
LEFT JOIN LATERAL (
SELECT count(*) AS ct
FROM ...
) ure ON u.access < 3
WHERE u.id = :id;
Run Code Online (Sandbox Code Playgroud)
结果一样。
归档时间: |
|
查看次数: |
11719 次 |
最近记录: |