加入时 Postgres 子查询 运行 非常慢
Postgres subqueries running extremely slowly when joined
我尝试使用 Postgres 查询时遇到问题 运行 - 我已经尝试了很多方法来确定问题的框架,但到目前为止没有任何乐趣。
我已经设法编写了一些有效的查询,但症结之一是性能 - 有效的查询太慢而无法使用。
我有一个名为 events_hub
的 table,它链接到包含不同事件信息的单独 table。不同的事件用不同的event_types
来区分。这些事件也被分组到聚合中,聚合以aggregate_id
.
区分
我的基本问题是,我想为每个聚合组找到与事件 1 关联的最早时间,然后计算事件 2 在导致该时间的 window 时间内发生的次数(例如,计算事件 2 在聚合组最早出现之前的 24 小时内发生的次数)。
事件中心 table 如下所示:
| aggregate_id | event_id | event_type | event_time |
-------------------------------------------------------
| 1 | 1 | 1 | 1st Jan |
| 1 | 2 | 1 | 2nd Jan |
| 2 | 3 | 1 | 2nd Jan |
| 2 | 4 | 1 | 3rd Jan |
| null | 5 | 2 | 30th Dec |
| null | 6 | 2 | 31st Dec |
| null | 7 | 2 | 1st Jan |
| null | 8 | 2 | 1st Jan |
-------------------------------------------------------
在上面的玩具示例中,我想要 return:
| aggregate_id | count_of_event2 |
----------------------------------
| 1 | 3 |
| 2 | 2 |
----------------------------------
因为最早出现的aggregate_id1在前一天出现了3次event_type2,而aggregate_id2只有2次
方法一
我的第一次尝试涉及使用由 group by 包围的联接。以下查询 运行 非常快,但并不 return 我想要的:
SELECT
aggregate_id,
count(aggregate_id)
FROM
(SELECT
aggregate_id,
min(event_time) as time_of_event1
FROM events_hub WHERE event_type = 1
GROUP BY aggregate_id) as t1
LEFT JOIN
(SELECT event_time as time_of_event2
FROM events_hub WHERE event_type = 2) as t2
ON t2.time_of_event2 BETWEEN t1.time_of_event1 - INTERVAL '24 hours'
AND t1.time_of_event1
GROUP BY aggregate_id
运行 EXPLAIN ANALYZE
关于这个 return 以下内容(请注意,这个问题中的 SQL 查询是我想要的实际查询的简化版本到 运行 - 因此在解释计划中出现的 table 有一些额外的限制):
HashAggregate (cost=1262545.21..1262547.21 rows=200 width=15) (actual time=536.206..539.222 rows=2824 loops=1)
Group Key: events_hub_1.aggregate_id
-> Nested Loop Left Join (cost=9137.36..1191912.59 rows=14126523 width=15) (actual time=15.419..395.895 rows=111948 loops=1)
-> HashAggregate (cost=9136.80..9141.42 rows=462 width=23) (actual time=15.387..19.316 rows=2824 loops=1)
Group Key: events_hub_1.aggregate_id
-> Index Only Scan using comp_index1 on events_hub events_hub_1 (cost=0.56..9110.87 rows=5186 width=23) (actual time=2.669..9.750 rows=4412 loops=1)
Index Cond: ((event_type_code = 5) AND (event_datetime >= '2013-01-01 00:00:00'::timestamp without time zone) AND (event_datetime <= '2013-01-02 00:00:00'::timestamp without time zone) AND (aggregate_id IS NOT NULL))
Heap Fetches: 4412
-> Index Only Scan using comp_index on events_hub (cost=0.56..2254.33 rows=30577 width=8) (actual time=0.005..0.049 rows=40 loops=2824)
Index Cond: ((event_type_code = 3) AND (event_datetime <= (min(events_hub_1.event_datetime))) AND (event_datetime >= ((min(events_hub_1.event_datetime)) - '12:00:00'::interval)))
Heap Fetches: 0
Planning time: 0.326 ms
Execution time: 542.020 ms
这并不特别令人惊讶,因为我在事件中心上有一个复合索引 (event_type, event_time)
,因此基于 2 个事件 运行 的相对时间的相对复杂的连接条件很快。
但是,当我尝试根据事件 2 的某些属性向查询添加另一个条件(以获得我需要的结果)时,查询速度急剧下降(如上面的查询在一个闪光灯,而下面的将 运行s 持续几分钟):
SELECT
aggregate_id,
count(aggregate_id)
FROM
(SELECT
aggregate_id,
min(event_time) as time_of_event1
FROM events_hub WHERE event_type = 1
GROUP BY aggregate_id) as t1
LEFT JOIN
(SELECT event_id, event_time as time_of_event2
FROM events_hub WHERE event_type = 2) as t2
ON t2.time_of_event2 BETWEEN t1.time_of_event1 - INTERVAL '24 hours'
AND t1.time_of_event1
INNER JOIN
(SELECT event_id FROM event_2_attributes WHERE some_flag = TRUE) as t3
ON t2.event_id = t3.event_id
GROUP BY aggregate_id
对于此查询,EXPLAIN ANALYZE
查询 returns:
HashAggregate (cost=33781.17..33783.17 rows=200 width=15) (actual time=479888.736..479891.819 rows=2824 loops=1)
Group Key: events_hub_1.aggregate_id
-> Nested Loop (cost=9625.94..33502.10 rows=55815 width=15) (actual time=346721.414..479857.494 rows=26164 loops=1)
Join Filter: ((events_hub.event_datetime <= (min(events_hub_1.event_datetime))) AND (events_hub.event_datetime >= ((min(events_hub_1.event_datetime)) - '12:00:00'::interval)))
Rows Removed by Join Filter: 209062796
-> Merge Join (cost=489.14..14311.03 rows=1087 width=8) (actual time=1.360..1571.387 rows=74040 loops=1)
Merge Cond: (events_hub.event_id = arrests.event_id)
-> Index Scan using comp_index4 on events_hub (cost=0.44..290158.71 rows=275192 width=12) (actual time=1.344..512.787 rows=282766 loops=1)
Index Cond: (event_type_code = 3)
-> Index Scan using arrests_events_id_index on arrests (cost=0.42..11186.59 rows=73799 width=4) (actual time=0.008..456.550 rows=74040 loops=1)
Filter: felony_flag
Rows Removed by Filter: 210238
-> Materialize (cost=9136.80..9148.35 rows=462 width=23) (actual time=0.001..3.002 rows=2824 loops=74040)
-> HashAggregate (cost=9136.80..9141.42 rows=462 width=23) (actual time=10.963..14.006 rows=2824 loops=1)
Group Key: events_hub_1.aggregate_id
-> Index Only Scan using comp_index1 on events_hub events_hub_1 (cost=0.56..9110.87 rows=5186 width=23) (actual time=0.018..5.405 rows=4412 loops=1)
Index Cond: ((event_type_code = 5) AND (event_datetime >= '2013-01-01 00:00:00'::timestamp without time zone) AND (event_datetime <= '2013-01-02 00:00:00'::timestamp without time zone) AND (aggregate_id IS NOT NULL))
Heap Fetches: 4412
Planning time: 12.548 ms
Execution time: 479894.888 ms
请注意,当包含内部联接时,less 数据实际上是 returned。但它仍然 运行 慢了很多。
我试过将这些连接嵌套在彼此内部,并进行切换,使 RIGHT JOIN
而不是 LEFT JOIN
,但这没有任何区别。
我也为每个子查询尝试了 CTE 表达式来尝试强制执行顺序,但也没有成功。
方法二
作为第二种方法,我尝试使用 return 事件 2 计数的子查询:
SELECT
t1.aggregate_id,
(SELECT count(t3.event_id)
FROM (SELECT event_id FROM events_hub AS t2 WHERE t2.event_type = 2
AND t2.event_time BETWEEN t1.time_of_event1 - INTERVAL '24 hours'
AND t1.time_of_event1) as t3
INNER JOIN event_2_attributes as t4
ON t3.event_id = t4.event_id
WHERE t4.some_flag = TRUE) as count_column
FROM
(SELECT
aggregate_id,
min(event_time) as time_of_event1
FROM events_hub WHERE event_type = 1
GROUP BY aggregate_id) as t1
这很好用,大约 15 秒后 运行s。但是,当我尝试获取结果并将它们插入另一个 table(这是我接下来要做的事情所必需的)时,查询需要花费大量时间才能 运行:
CREATE TABLE tbl AS
< query above >
这让我感到莫名其妙!
我已尝试 运行 EXPLAIN ANALYZE
对此查询进行查询,但在退出前已达到 2000 秒。如上所述,如果没有 EXPLAIN ANALYZE
,这个 运行 会在 15 秒内完成。
方法 3
作为最后一种方法,我尝试使用横向连接如下(此处没有分组):
WITH t1 AS
(SELECT
aggregate_id,
min(event_time) as time_of_event1
FROM events_hub WHERE event_type = 1
GROUP BY aggregate_id)
SELECT
t1.aggregate_id,
t2.event_time
FROM t1
LEFT JOIN LATERAL
(SELECT event_time FROM
(SELECT event_id, event_time FROM events_hub WHERE event_type = 2) as t3
INNER JOIN
(SELECT event_id FROM event_2_attributes WHERE some_flag = TRUE) as t4
ON t3.event_id = t4.event_id
WHERE t3.event_time BETWEEN t1.time_of_event1 - INTERVAL '24 hours'
AND t1.time_of_event1
) as t2
ON TRUE
这个查询 运行s,但是同样非常非常慢 - 即使没有分组操作。
如果您能就这些(可能无关?)提供任何信息,我们将不胜感激。可能值得一提的是,事件中心中的每个列都已编入索引。
非常感谢!
不确定这是否有帮助,因为您没有包含 EXPLAIN ANALIZE
,但是当您创建子查询然后加入时,您通常会失去对索引的使用。
试试这样写
SELECT e.event_id, e.event_time, ea.event_id -- but dont think you need it repeat event_id
FROM events e
INNER JOIN event_2_attributes ea
ON e.event_id = ea.event_id
WHERE e.event_type = 2
AND ea.some_flag = TRUE
好的,我知道了。
虽然不是 'neatest' 的解决方案,但最后的技巧是创建一个 table 包含初始 GROUP BY
操作的结果,该操作 returns 最早关联aggregate_id
:
CREATE TABLE earliest_time AS
(SELECT
aggregate_id,
min(event_time) as time_of_event1
FROM events_hub WHERE event_type = 1
GROUP BY aggregate_id)
然后在 aggregate_id
和 time_of_event1
列上添加索引。
然后按照上面的方法 1 使用此 table。
子查询已经具体化有助于规划器选择最有效的路径,并且执行时间下降了 2 个数量级。
我尝试使用 Postgres 查询时遇到问题 运行 - 我已经尝试了很多方法来确定问题的框架,但到目前为止没有任何乐趣。
我已经设法编写了一些有效的查询,但症结之一是性能 - 有效的查询太慢而无法使用。
我有一个名为 events_hub
的 table,它链接到包含不同事件信息的单独 table。不同的事件用不同的event_types
来区分。这些事件也被分组到聚合中,聚合以aggregate_id
.
我的基本问题是,我想为每个聚合组找到与事件 1 关联的最早时间,然后计算事件 2 在导致该时间的 window 时间内发生的次数(例如,计算事件 2 在聚合组最早出现之前的 24 小时内发生的次数)。
事件中心 table 如下所示:
| aggregate_id | event_id | event_type | event_time |
-------------------------------------------------------
| 1 | 1 | 1 | 1st Jan |
| 1 | 2 | 1 | 2nd Jan |
| 2 | 3 | 1 | 2nd Jan |
| 2 | 4 | 1 | 3rd Jan |
| null | 5 | 2 | 30th Dec |
| null | 6 | 2 | 31st Dec |
| null | 7 | 2 | 1st Jan |
| null | 8 | 2 | 1st Jan |
-------------------------------------------------------
在上面的玩具示例中,我想要 return:
| aggregate_id | count_of_event2 |
----------------------------------
| 1 | 3 |
| 2 | 2 |
----------------------------------
因为最早出现的aggregate_id1在前一天出现了3次event_type2,而aggregate_id2只有2次
方法一
我的第一次尝试涉及使用由 group by 包围的联接。以下查询 运行 非常快,但并不 return 我想要的:
SELECT
aggregate_id,
count(aggregate_id)
FROM
(SELECT
aggregate_id,
min(event_time) as time_of_event1
FROM events_hub WHERE event_type = 1
GROUP BY aggregate_id) as t1
LEFT JOIN
(SELECT event_time as time_of_event2
FROM events_hub WHERE event_type = 2) as t2
ON t2.time_of_event2 BETWEEN t1.time_of_event1 - INTERVAL '24 hours'
AND t1.time_of_event1
GROUP BY aggregate_id
运行 EXPLAIN ANALYZE
关于这个 return 以下内容(请注意,这个问题中的 SQL 查询是我想要的实际查询的简化版本到 运行 - 因此在解释计划中出现的 table 有一些额外的限制):
HashAggregate (cost=1262545.21..1262547.21 rows=200 width=15) (actual time=536.206..539.222 rows=2824 loops=1)
Group Key: events_hub_1.aggregate_id
-> Nested Loop Left Join (cost=9137.36..1191912.59 rows=14126523 width=15) (actual time=15.419..395.895 rows=111948 loops=1)
-> HashAggregate (cost=9136.80..9141.42 rows=462 width=23) (actual time=15.387..19.316 rows=2824 loops=1)
Group Key: events_hub_1.aggregate_id
-> Index Only Scan using comp_index1 on events_hub events_hub_1 (cost=0.56..9110.87 rows=5186 width=23) (actual time=2.669..9.750 rows=4412 loops=1)
Index Cond: ((event_type_code = 5) AND (event_datetime >= '2013-01-01 00:00:00'::timestamp without time zone) AND (event_datetime <= '2013-01-02 00:00:00'::timestamp without time zone) AND (aggregate_id IS NOT NULL))
Heap Fetches: 4412
-> Index Only Scan using comp_index on events_hub (cost=0.56..2254.33 rows=30577 width=8) (actual time=0.005..0.049 rows=40 loops=2824)
Index Cond: ((event_type_code = 3) AND (event_datetime <= (min(events_hub_1.event_datetime))) AND (event_datetime >= ((min(events_hub_1.event_datetime)) - '12:00:00'::interval)))
Heap Fetches: 0
Planning time: 0.326 ms
Execution time: 542.020 ms
这并不特别令人惊讶,因为我在事件中心上有一个复合索引 (event_type, event_time)
,因此基于 2 个事件 运行 的相对时间的相对复杂的连接条件很快。
但是,当我尝试根据事件 2 的某些属性向查询添加另一个条件(以获得我需要的结果)时,查询速度急剧下降(如上面的查询在一个闪光灯,而下面的将 运行s 持续几分钟):
SELECT
aggregate_id,
count(aggregate_id)
FROM
(SELECT
aggregate_id,
min(event_time) as time_of_event1
FROM events_hub WHERE event_type = 1
GROUP BY aggregate_id) as t1
LEFT JOIN
(SELECT event_id, event_time as time_of_event2
FROM events_hub WHERE event_type = 2) as t2
ON t2.time_of_event2 BETWEEN t1.time_of_event1 - INTERVAL '24 hours'
AND t1.time_of_event1
INNER JOIN
(SELECT event_id FROM event_2_attributes WHERE some_flag = TRUE) as t3
ON t2.event_id = t3.event_id
GROUP BY aggregate_id
对于此查询,EXPLAIN ANALYZE
查询 returns:
HashAggregate (cost=33781.17..33783.17 rows=200 width=15) (actual time=479888.736..479891.819 rows=2824 loops=1)
Group Key: events_hub_1.aggregate_id
-> Nested Loop (cost=9625.94..33502.10 rows=55815 width=15) (actual time=346721.414..479857.494 rows=26164 loops=1)
Join Filter: ((events_hub.event_datetime <= (min(events_hub_1.event_datetime))) AND (events_hub.event_datetime >= ((min(events_hub_1.event_datetime)) - '12:00:00'::interval)))
Rows Removed by Join Filter: 209062796
-> Merge Join (cost=489.14..14311.03 rows=1087 width=8) (actual time=1.360..1571.387 rows=74040 loops=1)
Merge Cond: (events_hub.event_id = arrests.event_id)
-> Index Scan using comp_index4 on events_hub (cost=0.44..290158.71 rows=275192 width=12) (actual time=1.344..512.787 rows=282766 loops=1)
Index Cond: (event_type_code = 3)
-> Index Scan using arrests_events_id_index on arrests (cost=0.42..11186.59 rows=73799 width=4) (actual time=0.008..456.550 rows=74040 loops=1)
Filter: felony_flag
Rows Removed by Filter: 210238
-> Materialize (cost=9136.80..9148.35 rows=462 width=23) (actual time=0.001..3.002 rows=2824 loops=74040)
-> HashAggregate (cost=9136.80..9141.42 rows=462 width=23) (actual time=10.963..14.006 rows=2824 loops=1)
Group Key: events_hub_1.aggregate_id
-> Index Only Scan using comp_index1 on events_hub events_hub_1 (cost=0.56..9110.87 rows=5186 width=23) (actual time=0.018..5.405 rows=4412 loops=1)
Index Cond: ((event_type_code = 5) AND (event_datetime >= '2013-01-01 00:00:00'::timestamp without time zone) AND (event_datetime <= '2013-01-02 00:00:00'::timestamp without time zone) AND (aggregate_id IS NOT NULL))
Heap Fetches: 4412
Planning time: 12.548 ms
Execution time: 479894.888 ms
请注意,当包含内部联接时,less 数据实际上是 returned。但它仍然 运行 慢了很多。
我试过将这些连接嵌套在彼此内部,并进行切换,使 RIGHT JOIN
而不是 LEFT JOIN
,但这没有任何区别。
我也为每个子查询尝试了 CTE 表达式来尝试强制执行顺序,但也没有成功。
方法二
作为第二种方法,我尝试使用 return 事件 2 计数的子查询:
SELECT
t1.aggregate_id,
(SELECT count(t3.event_id)
FROM (SELECT event_id FROM events_hub AS t2 WHERE t2.event_type = 2
AND t2.event_time BETWEEN t1.time_of_event1 - INTERVAL '24 hours'
AND t1.time_of_event1) as t3
INNER JOIN event_2_attributes as t4
ON t3.event_id = t4.event_id
WHERE t4.some_flag = TRUE) as count_column
FROM
(SELECT
aggregate_id,
min(event_time) as time_of_event1
FROM events_hub WHERE event_type = 1
GROUP BY aggregate_id) as t1
这很好用,大约 15 秒后 运行s。但是,当我尝试获取结果并将它们插入另一个 table(这是我接下来要做的事情所必需的)时,查询需要花费大量时间才能 运行:
CREATE TABLE tbl AS
< query above >
这让我感到莫名其妙!
我已尝试 运行 EXPLAIN ANALYZE
对此查询进行查询,但在退出前已达到 2000 秒。如上所述,如果没有 EXPLAIN ANALYZE
,这个 运行 会在 15 秒内完成。
方法 3
作为最后一种方法,我尝试使用横向连接如下(此处没有分组):
WITH t1 AS
(SELECT
aggregate_id,
min(event_time) as time_of_event1
FROM events_hub WHERE event_type = 1
GROUP BY aggregate_id)
SELECT
t1.aggregate_id,
t2.event_time
FROM t1
LEFT JOIN LATERAL
(SELECT event_time FROM
(SELECT event_id, event_time FROM events_hub WHERE event_type = 2) as t3
INNER JOIN
(SELECT event_id FROM event_2_attributes WHERE some_flag = TRUE) as t4
ON t3.event_id = t4.event_id
WHERE t3.event_time BETWEEN t1.time_of_event1 - INTERVAL '24 hours'
AND t1.time_of_event1
) as t2
ON TRUE
这个查询 运行s,但是同样非常非常慢 - 即使没有分组操作。
如果您能就这些(可能无关?)提供任何信息,我们将不胜感激。可能值得一提的是,事件中心中的每个列都已编入索引。
非常感谢!
不确定这是否有帮助,因为您没有包含 EXPLAIN ANALIZE
,但是当您创建子查询然后加入时,您通常会失去对索引的使用。
试试这样写
SELECT e.event_id, e.event_time, ea.event_id -- but dont think you need it repeat event_id
FROM events e
INNER JOIN event_2_attributes ea
ON e.event_id = ea.event_id
WHERE e.event_type = 2
AND ea.some_flag = TRUE
好的,我知道了。
虽然不是 'neatest' 的解决方案,但最后的技巧是创建一个 table 包含初始 GROUP BY
操作的结果,该操作 returns 最早关联aggregate_id
:
CREATE TABLE earliest_time AS
(SELECT
aggregate_id,
min(event_time) as time_of_event1
FROM events_hub WHERE event_type = 1
GROUP BY aggregate_id)
然后在 aggregate_id
和 time_of_event1
列上添加索引。
然后按照上面的方法 1 使用此 table。
子查询已经具体化有助于规划器选择最有效的路径,并且执行时间下降了 2 个数量级。