Vad*_*hin 2 postgresql execution-plan postgresql-performance
我有以下查询:
select ro.*
from courier c1
join courier c2 on c2.real_physical_courier_1c_id = c1.real_physical_courier_1c_id
join restaurant_order ro on ro.courier_id = c2.id
left join jsonb_array_elements(items) jae on true
left join jsonb_array_elements(jae->'options') ji on true
inner join catalogue c on c.id in ((jae->'id')::int, (ji->'id')::int)
join restaurant r on r.id = ro.restaurant_id
where c1.id = '7b35cdab-b423-472a-bde1-d6699f6cefd3' and ro.status in (70, 73)
group by ro.order_id, r.id ;
Run Code Online (Sandbox Code Playgroud)
以下是占用约 95% 时间的查询计划的一部分:
-> Parallel Bitmap Heap Scan on restaurant_order ro (cost=23.87..2357.58 rows=1244 width=1257) (actual time=11.931..38.163 rows=98 loops=2)"
Recheck Cond: (status = ANY ('{70,73}'::integer[]))"
Heap Blocks: exact=28755"
-> Bitmap Index Scan on ro__status (cost=0.00..23.34 rows=2115 width=0) (actual time=9.168..9.168 rows=51540 loops=1)"
Index Cond: (status = ANY ('{70,73}'::integer[]))"
Run Code Online (Sandbox Code Playgroud)
我有一些问题。
Index Cond: (status = ANY ('{70,73}'::integer[]))"
并创建一个包含 28755 个元素的位图。它的键是相应表行的物理位置(由exact
inHeap Blocks
部分表示)。它是否正确?Recheck Cond
实际上并没有执行,因为堆块不是有损样式。位图堆扫描按元组的物理位置对位图进行排序,以实现顺序访问。然后它在两次 go ( ) 中顺序读取表数据loops=2
,并且获取的表行数不超过 196 行。那是对的吗?Heap Blocks: exact=28755
随时间变化很大。差异是两个数量级。比如昨天是500左右,为什么会这样呢?index cond
. 开销似乎很大:不是 ~200 个键,而是 28755 个!loops=2
),它应该花费更少的时间,不是吗?或者,按元组的物理位置进行位图排序是罪魁祸首吗?以防万一,这是一个完整的计划:
"Group (cost=51297.15..52767.65 rows=19998 width=1261) (actual time=42.555..42.555 rows=0 loops=1)"
" Group Key: ro.order_id, r.id"
" -> Gather Merge (cost=51297.15..52708.83 rows=11764 width=1261) (actual time=42.554..45.459 rows=0 loops=1)"
" Workers Planned: 1"
" Workers Launched: 1"
" -> Group (cost=50297.14..50385.37 rows=11764 width=1261) (actual time=38.850..38.850 rows=0 loops=2)"
" Group Key: ro.order_id, r.id"
" -> Sort (cost=50297.14..50326.55 rows=11764 width=1261) (actual time=38.850..38.850 rows=0 loops=2)"
" Sort Key: ro.order_id, r.id"
" Sort Method: quicksort Memory: 25kB"
" Worker 0: Sort Method: quicksort Memory: 25kB"
" -> Nested Loop (cost=31.84..45709.27 rows=11764 width=1261) (actual time=38.819..38.819 rows=0 loops=2)"
" -> Nested Loop Left Join (cost=27.21..5194.50 rows=5882 width=1325) (actual time=38.819..38.819 rows=0 loops=2)"
" -> Nested Loop Left Join (cost=27.20..5076.49 rows=59 width=1293) (actual time=38.818..38.818 rows=0 loops=2)"
" -> Nested Loop (cost=27.20..5074.49 rows=1 width=1261) (actual time=38.818..38.818 rows=0 loops=2)"
" -> Hash Join (cost=26.93..5073.59 rows=1 width=1257) (actual time=38.817..38.818 rows=0 loops=2)"
" Hash Cond: (c2.real_physical_courier_1c_id = c1.real_physical_courier_1c_id)"
" -> Nested Loop (cost=24.28..5068.22 rows=1038 width=1267) (actual time=11.960..38.732 rows=98 loops=2)"
" -> Parallel Bitmap Heap Scan on restaurant_order ro (cost=23.87..2357.58 rows=1244 width=1257) (actual time=11.931..38.163 rows=98 loops=2)"
" Recheck Cond: (status = ANY ('{70,73}'::integer[]))"
" Heap Blocks: exact=28755"
" -> Bitmap Index Scan on ro__status (cost=0.00..23.34 rows=2115 width=0) (actual time=9.168..9.168 rows=51540 loops=1)"
" Index Cond: (status = ANY ('{70,73}'::integer[]))"
" -> Index Scan using courier_pkey on courier c2 (cost=0.41..2.18 rows=1 width=26) (actual time=0.005..0.005 rows=1 loops=195)"
" Index Cond: (id = ro.courier_id)"
" -> Hash (cost=2.63..2.63 rows=1 width=10) (actual time=0.039..0.039 rows=1 loops=2)"
" Buckets: 1024 Batches: 1 Memory Usage: 9kB"
" -> Index Scan using courier_pkey on courier c1 (cost=0.41..2.63 rows=1 width=10) (actual time=0.034..0.034 rows=1 loops=2)"
" Index Cond: (id = '7b35cdab-b423-472a-bde1-d6699f6cefd3'::uuid)"
" -> Index Only Scan using restaurant_pkey on restaurant r (cost=0.27..0.89 rows=1 width=4) (never executed)"
" Index Cond: (id = ro.restaurant_id)"
" Heap Fetches: 0"
" -> Function Scan on jsonb_array_elements jae (cost=0.00..1.00 rows=100 width=32) (never executed)"
" -> Function Scan on jsonb_array_elements ji (cost=0.01..1.00 rows=100 width=32) (never executed)"
" -> Bitmap Heap Scan on catalogue c (cost=4.63..6.87 rows=2 width=4) (never executed)"
" Recheck Cond: ((id = ((jae.value -> 'id'::text))::integer) OR (id = ((ji.value -> 'id'::text))::integer))"
" -> BitmapOr (cost=4.63..4.63 rows=2 width=0) (never executed)"
" -> Bitmap Index Scan on catalogue_pkey (cost=0.00..0.97 rows=1 width=0) (never executed)"
" Index Cond: (id = ((jae.value -> 'id'::text))::integer)"
" -> Bitmap Index Scan on catalogue_pkey (cost=0.00..0.97 rows=1 width=0) (never executed)"
" Index Cond: (id = ((ji.value -> 'id'::text))::integer)"
"Planning Time: 1.113 ms"
"Execution Time: 45.588 ms"
Run Code Online (Sandbox Code Playgroud)
它构建了 51,540 个项目的位图。然后将其(大致)分成两半,一个用于两个并行进程中的每一个。的报告exact=28755
显然仅针对其中一个进程。(如果您通过 禁用并行查询set max_parallel_workers_per_gather TO 0
,则生成的计划将更容易理解。这通常是我在研究查询性能时所做的第一件事,除非并行化正是我正在尝试研究的东西。无论我做出什么改进,然后通常会转换回并行执行。)
位图本质上是按物理顺序排列的。对它进行排序并不是与创建它不同的单独步骤。PostgreSQL 按照该顺序一一读取块。如果操作系统/文件系统决定将这些单独的读取合并为顺序读取,则由操作系统/文件系统决定。根据我的经验,在发挥良好效果之前,你必须阅读几乎所有的内容。如果您只读取每五个(随机)块,那么您还不如进行随机读取。我无法从您的数据中看出 28755 个块代表表中的哪一部分。
现在,为什么在位图索引扫描阶段创建的位图有这么多键?有 ro__status 索引,可以表明状态为 70 和 73 的记录只有大约 200 条
PostgreSQL 中的索引本身并不包含任何可见性信息。“ro__status”无法知道哪些 ctid 仍然可见,因此必须将它们全部填充到位图中。然后它们中的大多数在堆扫描阶段被拒绝作为不可见的。(这没有明确报告,就像重新检查和过滤器拒绝的方式一样。您必须通过位图大小和最终行数之间的差异来推断它。在 BitmapAnd 和 BitmapOr 的情况下,您甚至不能轻松地这样做,因为位图大小不准确)。
这就是问题的关键,您访问了 50,000 多个元组,只是为了找到 195 个活动元组。从索引中清除那些死元组是清理的主要工作之一。因此,您的桌子可能没有得到足够的吸尘。您可以非常简单地对此进行测试,用吸尘器清理桌子,看看是否可以解决问题。如果没有,那么您可能拥有长期保存的快照,这些快照阻止了清理的有效性,因此请去寻找它们。
Btree 索引确实具有“微真空”功能,其中定期索引扫描会杀死它发现指向全部堆元组的索引元组。但位图索引扫描并没有实现这一点,因为它们在初次协商后不会重新访问索引,因此没有杀死索引元组的好机会。位图扫描将受益于该微真空,但本身不会执行微真空。这可能会导致一种反常的情况,即位图扫描越多,索引的相关部分就会变得越臃肿,直到位图扫描开始表现更差。增加清理可以解决这个问题,但如果您不想进一步增加它,那么您通常可以阻止位图。增加 effective_cache_size 可能是实现此目的的一种方法(但前提是您确实有 RAM 来支持这一增加)。
归档时间: |
|
查看次数: |
973 次 |
最近记录: |