在缺少索引的理论场景中,带限制的订单必须扫描所有数据,然后应用订单,然后仅应用限制,因为我们只能在获得前10行(例如)之后排序
但是postgres在这里并不聪明,下面的计划就是这个故事。
按限制排序
learning=# explain (analyze,buffers) select * from temp order by userid limit 10;
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------
Limit (cost=81745.51..81745.54 rows=10 width=41) (actual time=2064.275..2064.278 rows=10 loops=1)
Buffers: shared hit=13 read=18644
-> Sort (cost=81745.51..86735.41 rows=1995958 width=41) (actual time=2064.273..2064.274 rows=10 loops=1)
Sort Key: userid
Sort Method: top-N heapsort Memory: 25kB
Buffers: shared hit=13 read=18644
-> Seq Scan on temp (cost=0.00..38613.58 rows=1995958 width=41) (actual time=35.053..1652.660 rows=1995958 loops=1)
Buffers: shared hit=10 read=18644
Planning time: 0.167 ms
Execution time: 2064.335 ms
(10 rows)
无限制地订购
learning=# explain (analyze,buffers) select * from temp order by userid;
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------
Sort (cost=308877.61..313867.51 rows=1995958 width=41) (actual time=2685.680..3293.698 rows=1995958 loops=1)
Sort Key: userid
Sort Method: external merge Disk: 99504kB
Buffers: shared hit=42 read=18612, temp read=12440 written=12440
-> Seq Scan on temp (cost=0.00..38613.58 rows=1995958 width=41) (actual time=0.069..286.556 rows=1995958 loops=1)
Buffers: shared hit=42 read=18612
Planning time: 0.066 ms
Execution time: 3540.545 ms
(8 rows)
我的假设是postgres使用一种称为堆排序的算法(众所周知),并在获得前N行(限制)行时停止。
从this可视化,我无法理解这是如何工作的?任何人都能理解这一点。我的上述假设是正确的吗?
答案 0 :(得分:2)
我还没有深入阅读来源,但据我所知,它保持了一个有限大小的堆。
按顺序使用输入值。它将堆填充到目标数量的元组后,它开始检查每个新值,看它是否大于所有当前值,小于所有当前值,或者是否适合堆内。
如果它大于所有当前值(假设ASC排序),它将被丢弃,因为我们已经有足够低的值。
如果它小于所有当前值或某些当前值,它会在堆中的适当位置插入,所有内容都会向下移动一个,并且它会使堆中的最后一个条目崩溃
在src/backend/utils/sort/tuplesort.c
的{{1}}周围1618(git master中)case TSS_BOUNDED:
附近puttuple_common
。
答案 1 :(得分:0)
您链接到的可视化只是一个堆栈执行。堆数据结构是(二进制)树,其属性是每个节点中的值比其子节点中的值更大(或更小)。堆可以存储在数组中,这是可视化显示的内容。
堆可以用作有效的优先级队列。因为它只是部分排序,所以使用优先级队列比排序列表或类似的更便宜。这可能是Postgres所做的:如果从表中选择N个最大值,Postgres会保留到目前为止看到的N个最大值的优先级队列(由堆实现),其中最小的值是第一个(即在顶部堆)。如果新值甚至小于优先级队列的第一项,则可以丢弃新值。如果它更大,则从优先级队列中删除第一个值并插入新值。
答案 2 :(得分:0)
PostgreSQL为此使用“ top-N排序堆排序”。在像这样的查询上执行“ EXPLAIN ANALYZE”时,您可以看到该效果。 没有索引,就无法避免全表扫描。但是,top-N堆排序避免为所有行分配内存,因为 只关心前10个。
例如,我周围有一个600万的条目表,并要求一个未索引的列排在前10行。在应用LIMIT 10的情况下,它告诉我:
Sort Method: top-N heapsort Memory: 25kB
Without:
Sort Method: quicksort Memory: (some value)kB
因此,如果Postgres必须存储所有排序的600万行,则将需要那么多MB的工作内存。如果(请参阅SHOW work_mem)它低于该值 这将导致它向磁盘写入大量临时文件:
Sort Method: external merge Disk: 99504kB
要使用快速内存扫描,您必须增加work_mem参数