這代表:即使被掃描的索引範圍與 order by 子句的順序完全相反,管線化 order by 依然可能成立。雖然 order by 中的 ASC / DESC 修飾詞可能阻斷管線化執行,多數資料庫都提供簡單的方式調整索引順序,讓索引重新可用。

反向索引掃描#

以下查詢取回「昨天以來的銷售」,依日期與 PRODUCT_ID 遞減排序:

SELECT sale_date, product_id, quantity
  FROM sales
 WHERE sale_date >= TRUNC(sysdate) - INTERVAL '1' DAY
 ORDER BY sale_date DESC, product_id DESC;
---------------------------------------------------------------
|Id |Operation                     | Name        | Rows | Cost |
---------------------------------------------------------------
| 0 |SELECT STATEMENT              |             |  320 |  300 |
| 1 | TABLE ACCESS BY INDEX ROWID  | SALES       |  320 |  300 |
|*2 |  INDEX RANGE SCAN DESCENDING | SALES_DT_PR |  320 |    4 |
---------------------------------------------------------------

圖 6.2:反向索引掃描

資料庫用索引樹找到最後一個匹配條目,再沿葉節點鏈「往上」走。這正是資料庫要用雙向鏈結串列來串接葉節點的原因。

當然,關鍵前提是:被掃描的索引範圍與 order by 所需順序恰好相反

混用 ASC 與 DESC 時的斷裂#

下面這段就不滿足前提,因為它在 order by 中混用ASCDESC

SELECT sale_date, product_id, quantity
  FROM sales
 WHERE sale_date >= TRUNC(sysdate) - INTERVAL '1' DAY
 ORDER BY sale_date ASC, product_id DESC;

查詢必須先交出昨天的銷售(依 PRODUCT_ID 遞減),再交出今天的銷售(同樣依 PRODUCT_ID 遞減)。

圖 6.3:不可能的管線化 order by——資料庫得在索引掃描中「跳躍」

索引中並沒有「從昨天最小的 PRODUCT_ID 連到今天最大的 PRODUCT_ID」的連結。資料庫因此無法用這個索引避開明確排序。

解法:在索引定義中指定方向#

多數資料庫都提供簡單的方式把索引順序調成與 order by 一致——在索引宣告中使用 ASC / DESC 修飾詞:

  DROP INDEX sales_dt_pr;

CREATE INDEX sales_dt_pr
    ON sales (sale_date ASC, product_id DESC);

現在索引順序與 order by 一致,排序操作再度消失:

---------------------------------------------------------------
|Id | Operation                   | Name        | Rows | Cost |
---------------------------------------------------------------
| 0 | SELECT STATEMENT            |             |  320 |  301 |
| 1 | TABLE ACCESS BY INDEX ROWID | SALES       |  320 |  301 |
|*2 |   INDEX RANGE SCAN          | SALES_DT_PR |  320 |    4 |
---------------------------------------------------------------

圖 6.4:混合順序的索引

第二欄排序方向的改變,等於把前一張圖的箭頭方向對調,使第一個箭頭的終點接上第二個箭頭的起點。索引因而具備所需順序,不再需要「跳躍」

當 order by 中混用 ASCDESC 時,索引定義也必須照樣混用,才能用於管線化 order by。

不影響該索引對 where 子句的可用性。

什麼時候才真的需要 ASC/DESC 索引#

ASC/DESC 索引只在個別欄位需要相反方向排序時才需要。若是要把所有欄位的順序全部反轉,並不需要——資料庫本來就能反向讀取索引。

NULLS FIRST / NULLS LAST#

除了 ASCDESC,SQL 標準還定義了兩個鮮為人知的 order by 修飾詞:NULLS FIRSTNULLS LAST。對 NULL 排序的明確控制是 SQL:2003 才「新近」加入的選用擴充,因此資料庫支援相當稀疏。

這特別令人擔憂,因為標準並未確切定義 NULL 的排序位置:它只規定排序後所有 NULL 必須聚在一起,卻未指定該在其他條目之前或之後。

嚴格說來,你其實得為 order by 中所有可能為 NULL 的欄位都指定 NULL 排序,才能得到明確定義的行為。

各家支援狀況:

  • SQL Server 2012 與 MySQL 5.6:均未實作這項選用擴充。
  • Oracle Database:早在標準納入之前就支援 NULLS 排序,但直到 11g 仍不接受它出現在索引定義中——因此以 NULLS FIRST 排序時無法做管線化 order by。
  • PostgreSQL:自 8.3 起,order by 子句與索引定義兩處都支援 NULLS 修飾詞。

圖 6.5:資料庫/功能對照表