Algorithms for External Sorting
Sorting supports ORDER BY, duplicate elimination, grouping, set operations, index creation, and sort-merge join. If the input fits in memory, an internal sorting algorithm is sufficient. Database relations often exceed available memory, so the DBMS must sort while moving data between memory and secondary storage.
An external sorting algorithm is designed for this situation. Its dominant cost is normally block I/O rather than CPU comparisons.
Notation and Constraints
Let:
be the number of blocks in the input file; be the number of memory buffer blocks available to the sort; be the number of initial sorted runs; be the merge degree, the number of runs merged at once.
When
External merge sorting needs at least three buffers: two input buffers and one output buffer. Thus
Sorted run
A run is a sequence of blocks whose records are already sorted on the requested sort key. Each initial run fits in memory when it is created, but the final run may be as large as the whole input file.
External Sort-Merge Algorithm
External sort-merge has two major phases:
- Run generation: read memory-sized portions, sort them internally, and write sorted runs.
- Multiway merge: merge several runs per pass until only one sorted run remains.
Phase 1 — Run generation
Repeat until the entire input has been consumed:
- Read up to
input blocks into memory. - Sort their records using an internal sorting algorithm.
- Write the sorted records as one run on disk.
The number of initial runs is:
If the final chunk contains fewer than
Unsorted file
┌────┬────┬────┬────┬────┬────┬────┬────┐
│ memory-sized chunks, each sorted │
└────┴────┴────┴────┴────┴────┴────┴────┘
↓
run 1 run 2 run 3 ...
[sorted] [sorted] [sorted]Every block is read once and written once during run generation, so this phase costs approximately:
Phase 2 — Multiway merging
During merging, one buffer is reserved for output. The remaining
For a
- Load the first block of each input run into its input buffer.
- Compare the smallest unconsumed record from every input buffer.
- Move the smallest record to the output buffer.
- Refill an input buffer when its block is exhausted.
- Write the output buffer when it becomes full.
- Continue until every input run has been consumed.
One merge produces a larger sorted run because the next output record is always the smallest remaining record among all participating runs.
After one full merge pass, the number of runs falls from
The merge passes continue until this number becomes one.
Number of Passes and I/O Cost
Assuming a merge fan-in of
Each complete pass reads all
This formula counts writing the final sorted file. If the next operator consumes the final merge output directly, the last write—and possibly a later read—can be avoided through pipelining.
Count block I/O, not just comparisons
An
Worked Example
Suppose an Orders file occupies:
and the sorting operator receives:
Generate initial runs
Each memory load sorts at most 11 blocks:
Run generation reads and writes the file once:
Merge the runs
One output buffer leaves ten input buffers, so the maximum merge degree is:
The runs shrink as follows:
91 initial runs
↓ first merge pass, 10-way
10 runs
↓ second merge pass, 10-way
1 final sorted runThus:
and the total cost is approximately:
The same 1,000-block file is processed three times: once to create runs and twice to merge them.
Why More Memory Helps Discontinuously
Adding buffer blocks helps in two ways:
- initial runs become longer, reducing
; - the merge fan-in
becomes larger.
The benefit is stepwise rather than perfectly smooth. Enough extra memory to eliminate an entire merge pass saves approximately one full read and one full write of the file:
For a one-pass merge after run generation, all initial runs must be merged at once:
Ignoring rounding, this condition is roughly
Buffering Details
Input and output buffers
The simplest multiway merge assigns one block to each input run and one block to output. This arrangement maximizes fan-in, but single-block buffers can lead to frequent I/O requests.
A system may instead allocate several blocks per run and several blocks to output. Larger sequential I/O requests can improve transfer efficiency, although fewer input runs can then participate in each merge.
Double buffering
With double buffering, one buffer is processed while another buffer for the same stream is being filled or written. This overlaps CPU work with I/O:
CPU consumes buffer A ←→ storage fills buffer B
CPU consumes buffer B ←→ storage fills buffer ADouble buffering can reduce waiting time, but it consumes more memory and can reduce the merge degree if the total memory budget is fixed.
Blocking and record boundaries
The sort operates on records but performs I/O in blocks. Variable-length records and records spanning blocks require the storage layer to preserve record boundaries while input buffers are refilled and output blocks are formed.
Sorting Records versus Sorting Pointers
A DBMS does not always need to copy complete records during sorting. It can sort smaller entries of the form:
This creates more entries per block and can reduce the number of blocks participating in the sort. The final operator follows the pointers to retrieve records when needed.
The trade-off is locality. If sorted pointers refer to records scattered across the data file, retrieving the full records may cause many random block accesses. Sorting complete records performs more work during sorting but produces the final records in sequential order.
Exploiting Existing Order
An explicit external sort may be unnecessary when:
- the file is physically ordered on a compatible key;
- a B⁺-tree index can be scanned in the required order;
- an earlier operator already produces the requested order;
- only small groups need additional sorting because the input has a useful prefix order.
However, scanning a secondary index does not guarantee cheap record retrieval. The index entries are ordered, but their referenced data records may be distributed across many blocks.
Interesting order
An intermediate order can be valuable even when the current operator does not require it. The optimizer may preserve an order because a later merge join, grouping, duplicate elimination, or ORDER BY can reuse it.
Where External Sorting Is Used
ORDER BY
The final tuples are sorted by the requested expressions unless an earlier access path already supplies that order.
Duplicate elimination
After sorting on all projected attributes, equal tuples become adjacent and all but one can be discarded during the merge.
Grouping and aggregation
Sorting on grouping attributes places each group together. Aggregate state can be finalized when the group key changes.
Sort-merge join
Both join inputs are sorted on their join attributes. A merge scan then advances through the ordered inputs to find matching groups.
Index construction
Bulk loading a B⁺-tree starts by sorting index entries on the search key, then packs leaf pages and builds upper levels.
External Sorting — Summary
- External sorting is required when the input does not fit in the available buffers.
- Run generation creates
sorted runs and costs about block transfers. - A multiway merge normally uses
input buffers and one output buffer. - Each merge pass reads and writes the entire file; eliminating one pass saves about
transfers. - More memory increases both initial run length and merge fan-in.
- Buffering, pointer sorting, existing order, and pipelining can materially change the real cost.