如何针对大数据量优化过滤器?数据库
How to optimize filter for big data volume? PostgreSQL
几周前,我们的团队在 SQL 查询方面遇到困难,因为数据量增加了很多。
对于如何更新架构或优化查询以保持 status
过滤逻辑相同的任何建议,我们将不胜感激。
简而言之:
我们有两个 tables a
和 b
。 b
有 FK 到 a
作为 M-1。
一个
id | processed
1 TRUE
2 TRUE
b
a_id| status | type_id | l_id
1 '1' 5 105
1 '3' 6 105
2 '2' 7 105
对于 (l_id
、type_id
、a_id
) 的唯一组合,我们只能有一个状态。
我们需要计算 a
行的计数,这些行是按 b
中的状态筛选的,按 a_id
分组。
在 table a
中,我们有 5 300 000 行。
在 table b
750 000 000 行中。
因此我们需要通过以下规则计算每个 a
行的状态:
对于 a_id b
中有 x 行:
1) 如果 x 的至少一个状态等于“3”,则 a_id
的状态为“3”。
2) 如果 x 的所有状态都等于 1
那么状态就是 1
.
以此类推
在目前的方法中,我们使用 array_agg() 函数来过滤子选择。所以我们的查询看起来像:
SELECT COUNT(*)
FROM (
SELECT
FROM (
SELECT at.id as id,
BOOL_AND(bt.processed) AS not_pending,
ARRAY_AGG(DISTINCT bt.status) AS status
FROM a AS at
LEFT OUTER JOIN b AS bt
ON (at.id = bt.a_id AND bt.l_id = 105 AND
bt.type_id IN (2,10,18,1,4,5,6))
WHERE at.processed = True
GROUP BY at.id) sub
WHERE not_pending = True
AND status <@ ARRAY ['1']::"char"[]
) counter
;
我们的计划如下:
Aggregate (cost=14665999.33..14665999.34 rows=1 width=8) (actual time=1875987.846..1875987.846 rows=1 loops=1)
-> GroupAggregate (cost=14166691.70..14599096.58 rows=5352220 width=37) (actual time=1875987.844..1875987.844 rows=0 loops=1)
Group Key: at.id
Filter: (bool_and(bt.processed) AND (array_agg(DISTINCT bt.status) <@ '{1}'::"char"[]))
Rows Removed by Filter: 5353930
-> Sort (cost=14166691.70..14258067.23 rows=36550213 width=6) (actual time=1860315.593..1864175.762 rows=37430745 loops=1)
Sort Key: at.id
Sort Method: external merge Disk: 586000kB
-> Hash Right Join (cost=1135654.48..8076230.39 rows=36550213 width=6) (actual time=55665.584..1846965.271 rows=37430745 loops=1)
Hash Cond: (bt.a_id = at.id)
-> Bitmap Heap Scan on b bt (cost=882095.79..7418660.65 rows=36704370 width=6) (actual time=51871.658..1826058.186 rows=37430378 loops=1)
Recheck Cond: ((l_id = 105) AND (type_id = ANY ('{2,10,18,1,4,5,6}'::integer[])))
Rows Removed by Index Recheck: 574462752
Heap Blocks: exact=28898 lossy=5726508
-> Bitmap Index Scan on db_page_index_atableobjects (cost=0.00..872919.69 rows=36704370 width=0) (actual time=51861.815..51861.815 rows=37586483 loops=1)
Index Cond: ((l_id = 105) AND (type_id = ANY ('{2,10,18,1,4,5,6}'::integer[])))
-> Hash (cost=165747.94..165747.94 rows=5352220 width=4) (actual time=3791.710..3791.710 rows=5353930 loops=1)
Buckets: 131072 Batches: 128 Memory Usage: 2507kB
-> Seq Scan on a at (cost=0.00..165747.94 rows=5352220 width=4) (actual time=0.528..2958.004 rows=5353930 loops=1)
Filter: processed
Rows Removed by Filter: 18659
Planning time: 0.328 ms
Execution time: 1876066.242 ms
如您所见,执行查询的时间非常长,我们希望它至少小于 30 秒。
我们已经尝试了一些方法,例如使用 bitor() 而不是 array_agg() 和 LATERAL JOIN。但是他们没有给我们想要的性能,我们决定暂时使用物化视图。但我们仍在寻找任何其他解决方案,非常感谢任何建议!
启用 track_io_timing
的计划:
Aggregate (cost=14665999.33..14665999.34 rows=1 width=8) (actual time=2820945.285..2820945.285 rows=1 loops=1)
Buffers: shared hit=23 read=5998844, temp read=414465 written=414880
I/O Timings: read=2655805.505
-> GroupAggregate (cost=14166691.70..14599096.58 rows=5352220 width=930) (actual time=2820945.283..2820945.283 rows=0 loops=1)
Group Key: at.id
Filter: (bool_and(bt.processed) AND (array_agg(DISTINCT bt.status) <@ '{1}'::"char"[]))
Rows Removed by Filter: 5353930
Buffers: shared hit=23 read=5998844, temp read=414465 written=414880
I/O Timings: read=2655805.505
-> Sort (cost=14166691.70..14258067.23 rows=36550213 width=6) (actual time=2804900.123..2808826.358 rows=37430745 loops=1)
Sort Key: at.id
Sort Method: external merge Disk: 586000kB
Buffers: shared hit=18 read=5998840, temp read=414465 written=414880
I/O Timings: read=2655805.491
-> Hash Right Join (cost=1135654.48..8076230.39 rows=36550213 width=6) (actual time=55370.788..2791441.542 rows=37430745 loops=1)
Hash Cond: (bt.a_id = at.id)
Buffers: shared hit=15 read=5998840, temp read=142879 written=142625
I/O Timings: read=2655805.491
-> Bitmap Heap Scan on b bt (cost=882095.79..7418660.65 rows=36704370 width=6) (actual time=51059.047..2769127.810 rows=37430378 loops=1)
Recheck Cond: ((l_id = 105) AND (type_id = ANY ('{2,10,18,1,4,5,6}'::integer[])))
Rows Removed by Index Recheck: 574462752
Heap Blocks: exact=28898 lossy=5726508
Buffers: shared hit=13 read=5886842
I/O Timings: read=2653254.939
-> Bitmap Index Scan on db_page_index_atableobjects (cost=0.00..872919.69 rows=36704370 width=0) (actual time=51049.365..51049.365 rows=37586483 loops=1)
Index Cond: ((l_id = 105) AND (type_id = ANY ('{2,10,18,1,4,5,6}'::integer[])))
Buffers: shared hit=12 read=131437
I/O Timings: read=49031.671
-> Hash (cost=165747.94..165747.94 rows=5352220 width=4) (actual time=4309.761..4309.761 rows=5353930 loops=1)
Buckets: 131072 Batches: 128 Memory Usage: 2507kB
Buffers: shared hit=2 read=111998, temp written=15500
I/O Timings: read=2550.551
-> Seq Scan on a at (cost=0.00..165747.94 rows=5352220 width=4) (actual time=0.515..3457.040 rows=5353930 loops=1)
Filter: processed
Rows Removed by Filter: 18659
Buffers: shared hit=2 read=111998
I/O Timings: read=2550.551
Planning time: 0.347 ms
Execution time: 2821022.622 ms
您可以在将 table B 与 A 连接之前对其进行过滤和分组。并按 ID 对两个 table 进行排序,因为它在处理连接操作时提高了 table 扫描的速度.请检查此代码:
with at as (
select distinct at.id, at.processed
from a AS at
WHERE at.processed = True
order by at.id
),
bt as (
select bt.a_id, bt.l_id, bt.type_id, --BOOL_AND(bt.processed) AS not_pending,
ARRAY_AGG(DISTINCT bt.status) as status
from b AS bt
group by bt.a_id, bt.l_id, bt.type_id
having bt.l_id = 105 AND bt.type_id IN (2,10,18,1,4,5,6)
order by bt.a_id
),
counter as (
select at.id,
case
when '1' = all(status) then '1'
when '3' = any(status) then '3'
else status end as status
from at inner join bt on at.id=bt.a_id
)
select count (*) from counter where status='1'
在目前的计划中,几乎所有时间都用于读取位图堆扫描的 table 页。您必须已经有一个类似 (l_id, type_id)
的索引。如果您将它更改(创建一个新的,然后选择性地删除旧的)改为在 (ld_id, type_id, processed, a_id, status)
上,或者可能在 (ld_id, type_id, a_id, status) where processed)
上,那么它可能会切换到可以避免读取的仅索引扫描table 因为所有数据都存在于索引中。您将需要确保 table 已完全清空,此策略才能生效。我只是在构建索引之前手动清理 table 一次,然后如果它有效,你就可以担心如何保持它的良好清理。
另一种选择是提高 effective_io_concurrency(我只是将其设置为 20。如果可行;您可以多玩几次以找到最佳设置),以便多个 IO table 上的读取请求可以立即完成。这将有多有效将取决于您的 IO 系统,我不知道 db.r5.xlarge 的答案。仅索引扫描更好,因为它使用更少的资源,而这种方法只是更快地使用相同的资源。 (如果您同时有多个类似的查询 运行,那很重要。另外,如果您按 IO 付费,您希望查询更少,而不是相同的查询更快)
另一种选择是尝试通过从 a 到 b 的嵌套循环来完全改变计划的形状。为此,您需要一个 b 上的索引,其中包含 a_id 和 l_id 作为前导列(以任一顺序)。如果您已经有了这样的索引并且它不会自然地选择这样的计划,您可能可以通过 set enable_hashjoin=off.
强制我的直觉这是一个需要踢另一边 5,353,930 次的嵌套循环不是会比你目前拥有的更好,即使另一边有一个有效的索引。
几周前,我们的团队在 SQL 查询方面遇到困难,因为数据量增加了很多。
对于如何更新架构或优化查询以保持 status
过滤逻辑相同的任何建议,我们将不胜感激。
简而言之:
我们有两个 tables a
和 b
。 b
有 FK 到 a
作为 M-1。
一个
id | processed
1 TRUE
2 TRUE
b
a_id| status | type_id | l_id
1 '1' 5 105
1 '3' 6 105
2 '2' 7 105
对于 (l_id
、type_id
、a_id
) 的唯一组合,我们只能有一个状态。
我们需要计算 a
行的计数,这些行是按 b
中的状态筛选的,按 a_id
分组。
在 table a
中,我们有 5 300 000 行。
在 table b
750 000 000 行中。
因此我们需要通过以下规则计算每个 a
行的状态:
对于 a_id b
中有 x 行:
1) 如果 x 的至少一个状态等于“3”,则 a_id
的状态为“3”。
2) 如果 x 的所有状态都等于 1
那么状态就是 1
.
以此类推
在目前的方法中,我们使用 array_agg() 函数来过滤子选择。所以我们的查询看起来像:
SELECT COUNT(*)
FROM (
SELECT
FROM (
SELECT at.id as id,
BOOL_AND(bt.processed) AS not_pending,
ARRAY_AGG(DISTINCT bt.status) AS status
FROM a AS at
LEFT OUTER JOIN b AS bt
ON (at.id = bt.a_id AND bt.l_id = 105 AND
bt.type_id IN (2,10,18,1,4,5,6))
WHERE at.processed = True
GROUP BY at.id) sub
WHERE not_pending = True
AND status <@ ARRAY ['1']::"char"[]
) counter
;
我们的计划如下:
Aggregate (cost=14665999.33..14665999.34 rows=1 width=8) (actual time=1875987.846..1875987.846 rows=1 loops=1)
-> GroupAggregate (cost=14166691.70..14599096.58 rows=5352220 width=37) (actual time=1875987.844..1875987.844 rows=0 loops=1)
Group Key: at.id
Filter: (bool_and(bt.processed) AND (array_agg(DISTINCT bt.status) <@ '{1}'::"char"[]))
Rows Removed by Filter: 5353930
-> Sort (cost=14166691.70..14258067.23 rows=36550213 width=6) (actual time=1860315.593..1864175.762 rows=37430745 loops=1)
Sort Key: at.id
Sort Method: external merge Disk: 586000kB
-> Hash Right Join (cost=1135654.48..8076230.39 rows=36550213 width=6) (actual time=55665.584..1846965.271 rows=37430745 loops=1)
Hash Cond: (bt.a_id = at.id)
-> Bitmap Heap Scan on b bt (cost=882095.79..7418660.65 rows=36704370 width=6) (actual time=51871.658..1826058.186 rows=37430378 loops=1)
Recheck Cond: ((l_id = 105) AND (type_id = ANY ('{2,10,18,1,4,5,6}'::integer[])))
Rows Removed by Index Recheck: 574462752
Heap Blocks: exact=28898 lossy=5726508
-> Bitmap Index Scan on db_page_index_atableobjects (cost=0.00..872919.69 rows=36704370 width=0) (actual time=51861.815..51861.815 rows=37586483 loops=1)
Index Cond: ((l_id = 105) AND (type_id = ANY ('{2,10,18,1,4,5,6}'::integer[])))
-> Hash (cost=165747.94..165747.94 rows=5352220 width=4) (actual time=3791.710..3791.710 rows=5353930 loops=1)
Buckets: 131072 Batches: 128 Memory Usage: 2507kB
-> Seq Scan on a at (cost=0.00..165747.94 rows=5352220 width=4) (actual time=0.528..2958.004 rows=5353930 loops=1)
Filter: processed
Rows Removed by Filter: 18659
Planning time: 0.328 ms
Execution time: 1876066.242 ms
如您所见,执行查询的时间非常长,我们希望它至少小于 30 秒。 我们已经尝试了一些方法,例如使用 bitor() 而不是 array_agg() 和 LATERAL JOIN。但是他们没有给我们想要的性能,我们决定暂时使用物化视图。但我们仍在寻找任何其他解决方案,非常感谢任何建议!
启用 track_io_timing
的计划:
Aggregate (cost=14665999.33..14665999.34 rows=1 width=8) (actual time=2820945.285..2820945.285 rows=1 loops=1)
Buffers: shared hit=23 read=5998844, temp read=414465 written=414880
I/O Timings: read=2655805.505
-> GroupAggregate (cost=14166691.70..14599096.58 rows=5352220 width=930) (actual time=2820945.283..2820945.283 rows=0 loops=1)
Group Key: at.id
Filter: (bool_and(bt.processed) AND (array_agg(DISTINCT bt.status) <@ '{1}'::"char"[]))
Rows Removed by Filter: 5353930
Buffers: shared hit=23 read=5998844, temp read=414465 written=414880
I/O Timings: read=2655805.505
-> Sort (cost=14166691.70..14258067.23 rows=36550213 width=6) (actual time=2804900.123..2808826.358 rows=37430745 loops=1)
Sort Key: at.id
Sort Method: external merge Disk: 586000kB
Buffers: shared hit=18 read=5998840, temp read=414465 written=414880
I/O Timings: read=2655805.491
-> Hash Right Join (cost=1135654.48..8076230.39 rows=36550213 width=6) (actual time=55370.788..2791441.542 rows=37430745 loops=1)
Hash Cond: (bt.a_id = at.id)
Buffers: shared hit=15 read=5998840, temp read=142879 written=142625
I/O Timings: read=2655805.491
-> Bitmap Heap Scan on b bt (cost=882095.79..7418660.65 rows=36704370 width=6) (actual time=51059.047..2769127.810 rows=37430378 loops=1)
Recheck Cond: ((l_id = 105) AND (type_id = ANY ('{2,10,18,1,4,5,6}'::integer[])))
Rows Removed by Index Recheck: 574462752
Heap Blocks: exact=28898 lossy=5726508
Buffers: shared hit=13 read=5886842
I/O Timings: read=2653254.939
-> Bitmap Index Scan on db_page_index_atableobjects (cost=0.00..872919.69 rows=36704370 width=0) (actual time=51049.365..51049.365 rows=37586483 loops=1)
Index Cond: ((l_id = 105) AND (type_id = ANY ('{2,10,18,1,4,5,6}'::integer[])))
Buffers: shared hit=12 read=131437
I/O Timings: read=49031.671
-> Hash (cost=165747.94..165747.94 rows=5352220 width=4) (actual time=4309.761..4309.761 rows=5353930 loops=1)
Buckets: 131072 Batches: 128 Memory Usage: 2507kB
Buffers: shared hit=2 read=111998, temp written=15500
I/O Timings: read=2550.551
-> Seq Scan on a at (cost=0.00..165747.94 rows=5352220 width=4) (actual time=0.515..3457.040 rows=5353930 loops=1)
Filter: processed
Rows Removed by Filter: 18659
Buffers: shared hit=2 read=111998
I/O Timings: read=2550.551
Planning time: 0.347 ms
Execution time: 2821022.622 ms
您可以在将 table B 与 A 连接之前对其进行过滤和分组。并按 ID 对两个 table 进行排序,因为它在处理连接操作时提高了 table 扫描的速度.请检查此代码:
with at as (
select distinct at.id, at.processed
from a AS at
WHERE at.processed = True
order by at.id
),
bt as (
select bt.a_id, bt.l_id, bt.type_id, --BOOL_AND(bt.processed) AS not_pending,
ARRAY_AGG(DISTINCT bt.status) as status
from b AS bt
group by bt.a_id, bt.l_id, bt.type_id
having bt.l_id = 105 AND bt.type_id IN (2,10,18,1,4,5,6)
order by bt.a_id
),
counter as (
select at.id,
case
when '1' = all(status) then '1'
when '3' = any(status) then '3'
else status end as status
from at inner join bt on at.id=bt.a_id
)
select count (*) from counter where status='1'
在目前的计划中,几乎所有时间都用于读取位图堆扫描的 table 页。您必须已经有一个类似 (l_id, type_id)
的索引。如果您将它更改(创建一个新的,然后选择性地删除旧的)改为在 (ld_id, type_id, processed, a_id, status)
上,或者可能在 (ld_id, type_id, a_id, status) where processed)
上,那么它可能会切换到可以避免读取的仅索引扫描table 因为所有数据都存在于索引中。您将需要确保 table 已完全清空,此策略才能生效。我只是在构建索引之前手动清理 table 一次,然后如果它有效,你就可以担心如何保持它的良好清理。
另一种选择是提高 effective_io_concurrency(我只是将其设置为 20。如果可行;您可以多玩几次以找到最佳设置),以便多个 IO table 上的读取请求可以立即完成。这将有多有效将取决于您的 IO 系统,我不知道 db.r5.xlarge 的答案。仅索引扫描更好,因为它使用更少的资源,而这种方法只是更快地使用相同的资源。 (如果您同时有多个类似的查询 运行,那很重要。另外,如果您按 IO 付费,您希望查询更少,而不是相同的查询更快)
另一种选择是尝试通过从 a 到 b 的嵌套循环来完全改变计划的形状。为此,您需要一个 b 上的索引,其中包含 a_id 和 l_id 作为前导列(以任一顺序)。如果您已经有了这样的索引并且它不会自然地选择这样的计划,您可能可以通过 set enable_hashjoin=off.
强制我的直觉这是一个需要踢另一边 5,353,930 次的嵌套循环不是会比你目前拥有的更好,即使另一边有一个有效的索引。