Top-N 查詢是指把結果限制為特定列數的查詢,常見於取出結果集中「最新」或「最好」的幾筆。要有效率地執行,排名必須靠管線化 order by 完成。
必須讓資料庫「知道」你只要幾列#
最單純的做法是抓完所需列數後就把 statement 關掉。可惜最佳化工具在準備執行計畫時無從預見這一點。
要挑出最佳計畫,最佳化工具必須知道應用程式最終是否會抓取所有列:
- 若會抓完,「全表掃描 + 明確排序」可能表現最好。
- 若只抓十列,管線化 order by 可能更好——即使資料庫得逐列抓取。
SQL 標準長期未涵蓋這項需求。對應的擴充 fetch first 遲至 SQL:2008 才引入,且目前僅 IBM DB2、PostgreSQL 與 SQL Server 2012 提供。一方面它是非核心擴充,另一方面各家資料庫多年來早就各自提供了專有解法。
各資料庫的 Top-N 語法#
以下都在做同一件事:取出所有銷售、從最新的開始,抓滿十列就中止執行。
MySQL / PostgreSQL(limit 子句)
SELECT *
FROM sales
ORDER BY sale_date DESC
LIMIT 10;Oracle Database(偽欄位 ROWNUM,需包一層)
SELECT *
FROM (
SELECT *
FROM sales
ORDER BY sale_date DESC
)
WHERE rownum <= 10;PostgreSQL(自 8.4 起支援 fetch first,舊的 limit 依然可用)
SELECT *
FROM sales
ORDER BY sale_date DESC
FETCH FIRST 10 ROWS ONLY;SQL Server(top 子句;2012 起也支援 fetch first)
SELECT TOP 10 *
FROM sales
ORDER BY sale_date DESC;上述查詢之所以特別,是因為資料庫認得它們是 top-N 查詢。
資料庫只有在一開始就知情,才能為部分結果最佳化查詢。
執行計畫:COUNT STOPKEY#
當最佳化工具知道我們只要十列時,只要可行,它就會偏好管線化 order by:
-------------------------------------------------------------
| Operation | Name | Rows | Cost |
-------------------------------------------------------------
| SELECT STATEMENT | | 10 | 9 |
| COUNT STOPKEY | | | |
| VIEW | | 10 | 9 |
| TABLE ACCESS BY INDEX ROWID| SALES |1004K | 9 |
| INDEX FULL SCAN DESCENDING| SALES_DT_PR | 10 | 3 |
-------------------------------------------------------------Oracle 以 COUNT STOPKEY 操作標示「計畫中的提前終止」——代表它認出了 top-N 語法。
附錄 A「執行計畫」整理了 MySQL、Oracle、PostgreSQL 與 SQL Server 的對應操作。
語法只是一半,索引才是另一半#
用對語法只解決一半問題,有效率地終止執行還要求底層操作以管線化方式執行——也就是 order by 子句必須被索引涵蓋(本例是 SALE_DATE 與 PRODUCT_ID 上的 SALE_DT_PR)。有了它,資料庫就能省去明確排序,邊從索引讀邊把列送給應用程式,抓滿十列即中止——讀取的列不會多於選出的列。
反之,若 SALE_DATE 上沒有合適索引可供管線化 order by,資料庫就必須讀取並排序整張表,要讀到表中最後一列,才能交出第一列:
--------------------------------------------------
| Operation | Name | Rows | Cost |
--------------------------------------------------
| SELECT STATEMENT | | 10 | 59558|
| COUNT STOPKEY | | | |
| VIEW | |1004K | 59558|
| SORT ORDER BY STOPKEY | |1004K | 59558|
| TABLE ACCESS FULL | SALES |1004K | 9246|
--------------------------------------------------這個計畫沒有管線化 order by,幾乎和「從客戶端主動中止」一樣慢。不過使用 top-N 語法仍然較好:資料庫不必具體化完整結果,只需具體化最新的十列,記憶體需求大幅降低。Oracle 以 SORT ORDER BY 上的 STOPKEY 修飾詞標示這項優化。
擴展性:回應時間幾乎與表大小無關#
管線化 top-N 查詢的好處不只是立即的效能收益,還有更好的擴展性:
- 未管線化:回應時間隨資料表大小成長(線性)。
- 管線化:回應時間只隨被選出的列數成長;換言之幾乎是常數,與表大小無關——只有當 B-tree 深度增加時才會稍微變慢。

圖 7.1:Top-N 查詢的擴展性
具體化(materialized)版本的回應時間隨資料量線性成長,管線化版本則維持恆定。
儘管回應時間不隨表大小成長,它仍隨被選出的列數成長:選兩倍的列,時間就加倍。
這對「載入下一頁」的分頁查詢特別要命——這類查詢常常又從第一筆開始讀起,把前一頁已顯示過的列全部讀進來再丟棄,才終於讀到第二頁的結果。
下一節會提供這個問題的解法。