Mar*_*tin 0 sql postgresql postgresql-performance lateral-join
这是将 SUM group by 添加到 COUNT DISTINCT 查询中的后续优化(尽管进行了一些优化和联接简化)。
我想知道是否可以优化以下需要完成的PostgreSQL 13.1查询130322.2ms。通常,如果只有一个JOIN LATERAL存在,它会在几毫秒内完成。
我最迷失的是,每个子查询JOIN LATERAL都有一个ON基于其自己的子查询分数的条件,我如何优化查询,可能减少子查询的数量JOIN LATERAL,但仍然得到相同的结果。
从我看来,当条件添加到代替OR中的某些 WHERE时,它似乎会变慢。看:JOIN LATERALAND
SELECT count(*)
FROM subscriptions q
JOIN LATERAL (
SELECT
SUM(ts.score) AS sum_score
FROM
quiz_answers qa
JOIN answers a ON a.id = qa.answer_id
JOIN tag_scores ts ON ts.answer_id = a.id
WHERE
qa.quiz_id = q.quiz_id
AND ts.tag_id = 21
) AS q62958 ON q62958.sum_score <= 1
JOIN LATERAL (
SELECT
SUM(ts.score) AS sum_score
FROM
quiz_answers qa
JOIN answers a ON a.id = qa.answer_id
JOIN tag_scores ts ON ts.answer_id = a.id
WHERE
qa.quiz_id = q.quiz_id
OR ts.tag_id = 32
) AS q120342 ON q120342.sum_score <= 1
JOIN LATERAL (
SELECT
SUM(ts.score) AS sum_score
FROM
quiz_answers qa
JOIN answers a ON a.id = qa.answer_id
JOIN tag_scores ts ON ts.answer_id = a.id
WHERE
qa.quiz_id = q.quiz_id
OR ts.tag_id = 35
) AS q992506 ON q992506.sum_score <= 1
JOIN LATERAL (
SELECT
SUM(ts.score) AS sum_score
FROM
quiz_answers qa
JOIN answers a ON a.id = qa.answer_id
JOIN tag_scores ts ON ts.answer_id = a.id
WHERE
qa.quiz_id = q.quiz_id
OR ts.tag_id = 33
) AS q343255 ON q343255.sum_score <= 1
JOIN LATERAL (
SELECT
SUM(ts.score) AS sum_score
FROM
quiz_answers qa
JOIN answers a ON a.id = qa.answer_id
JOIN tag_scores ts ON ts.answer_id = a.id
WHERE
qa.quiz_id = q.quiz_id
OR ts.tag_id = 29
) AS q532052 ON q532052.sum_score <= 1
JOIN LATERAL (
SELECT
SUM(ts.score) AS sum_score
FROM
quiz_answers qa
JOIN answers a ON a.id = qa.answer_id
JOIN tag_scores ts ON ts.answer_id = a.id
WHERE
qa.quiz_id = q.quiz_id
OR ts.tag_id = 30
) AS q268437 ON q268437.sum_score <= 1
JOIN LATERAL (
SELECT
SUM(ts.score) AS sum_score
FROM
quiz_answers qa
JOIN answers a ON a.id = qa.answer_id
JOIN tag_scores ts ON ts.answer_id = a.id
WHERE
qa.quiz_id = q.quiz_id
AND ts.tag_id = 46
) AS q553964 ON q553964.sum_score >= 3
JOIN LATERAL (
SELECT
SUM(ts.score) AS sum_score
FROM
quiz_answers qa
JOIN answers a ON a.id = qa.answer_id
JOIN tag_scores ts ON ts.answer_id = a.id
WHERE
qa.quiz_id = q.quiz_id
AND ts.tag_id = 24
) AS q928243 ON q928243.sum_score >= 2
WHERE
q.state = 'subscribed' AND q.app_id = 4
;
Run Code Online (Sandbox Code Playgroud)
该subscriptions表的行数少于 15000 行,与WHERE子句匹配的行数少于 2000 行。和q.state都有q.app_id索引。
完整EXPLAIN ANALYZE: https: //explain.depesz.com/s/Ok0h
主要问题是查询错误:
WHERE
qa.quiz_id = q.quiz_id
OR ts.tag_id = 32
Run Code Online (Sandbox Code Playgroud)
此处放错了位置,必须在聚合正确的行后OR引入。该子句包括具有匹配或 的所有行。所以所有的行,都是废话。WHEREquiz_id tag_id = 32
除此之外,您还可以将多个LATERAL子查询与条件聚合合并在一起,如下所示:
SELECT count(*)
FROM subscriptions q
JOIN LATERAL (
SELECT sum(ts.score) FILTER (WHERE ts.tag_id = 21) AS sum_score21
, sum(ts.score) FILTER (WHERE ts.tag_id = 32) AS sum_score32
, sum(ts.score) FILTER (WHERE ts.tag_id = 35) AS sum_score35
-- , more?
FROM quiz_answers qa
JOIN answers a ON a.id = qa.answer_id
JOIN tag_scores ts ON ts.answer_id = a.id
WHERE qa.quiz_id = q.quiz_id
AND ts.tag_id IN (21, 32, 35) -- more?
) AS t ON t.sum_score21 <= 1
OR t.sum_score32 <= 1
OR t.sum_score35 <= 1
-- AND / OR more?
WHERE q.state = 'subscribed'
AND q.app_id = 4;
Run Code Online (Sandbox Code Playgroud)
使用or添加更多条件时请注意运算符优先级:您可能需要括号作为之前的绑定。ANDORANDOR
关于聚合FILTER:
多列索引subscriptions(app_id, state, quizz_id)可能会有所帮助(为您提供仅索引扫描)。但由于桌子不是那么大,所以这并不重要。
LATERAL(而不是普通的子查询)仍然有意义,而外部过滤器消除了 table 中的大多数行subscriptions。多列索引tag_scores(answer_id, tag_id)可能会有所帮助。
随着子查询中标签的增多和/或订阅的增多,LATERAL所述索引的变体和有用性就会下降。
为了进行比较,这里有一个带有普通子查询的变体:
SELECT count(*)
FROM subscriptions q
JOIN (
SELECT qa.quiz_id
, sum(ts.score) FILTER (WHERE ts.tag_id = 21) AS sum_score21
, sum(ts.score) FILTER (WHERE ts.tag_id = 32) AS sum_score32
, sum(ts.score) FILTER (WHERE ts.tag_id = 35) AS sum_score35
-- , more?
FROM quiz_answers qa
JOIN answers a ON a.id = qa.answer_id
JOIN tag_scores ts ON ts.answer_id = a.id
WHERE ts.tag_id IN (21, 32, 35) -- more?
GROUP BY qa.quiz_id
) AS t USING (quiz_id)
WHERE q.state = 'subscribed'
AND q.app_id = 4
AND (t.sum_score21 <= 1
OR t.sum_score32 <= 1
OR t.sum_score35 <= 1)
-- AND / OR more?
;
Run Code Online (Sandbox Code Playgroud)
无论哪种方式,t.sum_score21 <= 1如果...
看起来像是一个非常狭窄的过滤器。
如果 in 中的行answers永远不会丢失(使用 FK 约束强制执行引用完整性?),您可以在此处删除中间人:
FROM quiz_answers qa
JOIN answers a ON a.id = qa.answer_id
JOIN tag_scores ts ON ts.answer_id = a.id
Run Code Online (Sandbox Code Playgroud)
-->
FROM quiz_answers qa
JOIN tag_scores ts USING (answer_id)
Run Code Online (Sandbox Code Playgroud)