http://www.tuicool.com/articles/bI3IBv
附問題:有以下一個SQL語句:
SELECT * FROM ( SELECT t.*, row_number() OVER (ORDER BY ID) rn FROM mytable t ) WHERE rn BETWEEN :start and :end
sql中的order by語句大大降低了處理的速度,如果把order by去掉,相應的執行計划會大大地提高。如果換成下面的sql:
SELECT t.*, row_number() OVER (ORDER BY ID) rn FROM mytable t WHERE rownum BETWEEN :start and :end
很明顯,這個sql是錯的,根本查詢不了正確的數據信息。是否有其它的方法可以提高查詢速度?
針對以上問題,就必須要了解一下關於row_number和rownum的區別,以及如何來運用這些信息。
首先了解一下rownum是如何進行工作的,根據oracle的官方文檔:
如果對rownum進行大於比較,這個比較將直接返回false。如,下列sql語句將不能返回任何數據信息:
SELECT * FROM employees WHERE ROWNUM > 1
在查詢中,第一條被命中的數據將賦予一個偽列rownum為1,那么這個條件就為false。第二條被命中的數據由於第一條的false將重新成為第一條數據,那么仍然賦值為1,顯示這個條件仍然為false。后續所有的數據將重復執行這個邏輯,最后一條數據也沒有返回。
這就是為什么之前的第2個查詢,應該轉換為以下的sql語句:
SELECT * FROM ( SELECT t.*, ROWNUM AS rn FROM mytable t ORDER BY paginator, id ) WHERE rn BETWEEN :start and :end
接下來,需要通過創建一些臨時數據表來查看這個sql語句的執行性能,我們將創建臨時表,追加索引,然后填充數據,最后分析這個sql語句的查詢信息。
CREATE TABLE mytable ( id NUMBER(10) NOT NULL, paginator NUMBER(10) NOT NULL, value VARCHAR2(50) ) / ALTER TABLE mytable ADD CONSTRAINT pk_mytable_id PRIMARY KEY (id) / CREATE INDEX ix_mytable_paginator_id ON mytable(paginator, id) / INSERT INTO mytable(id, paginator, value) SELECT level, level / 10000, 'Value ' || level FROM dual CONNECT BY level <= 1000000 / COMMIT / BEGIN DBMS_STATS.gather_schema_stats('"20090506_rownum"'); END; /
這個Sql語句創建一個包括100萬條數據的表,並且創建一個聯合索引.
同時,在這個查詢中,patinator字段是不是惟一的,是為了在之后展示這樣一種現象:
在查詢中,某些數據可能在不同的分頁查詢中出現多次,而某些數據則可能根據不會被查詢出
這就是所謂的分頁混亂。
然后,分別使用row_numer和rownum分別進行查詢,返回從900001到900010之間的10條數據信息。
row_number()
SELECT * FROM ( SELECT t.*, ROW_NUMBER() OVER (ORDER BY paginator, id) AS rn FROM mytable t ) WHERE rn BETWEEN 900001 AND 900010
| ID | PAGINATOR | VALUE | RN | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 900001 | 90 | Value 900001 | 900001 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 900002 | 90 | Value 900002 | 900002 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 900003 | 90 | Value 900003 | 900003 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 900004 | 90 | Value 900004 | 900004 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 900005 | 90 | Value 900005 | 900005 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 900006 | 90 | Value 900006 | 900006 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 900007 | 90 | Value 900007 | 900007 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 900008 | 90 | Value 900008 | 900008 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 900009 | 90 | Value 900009 | 900009 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 900010 | 90 | Value 900010 | 900010 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 10 rows fetched in 0.0005s (0.8594s) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
SELECT STATEMENT VIEW WINDOW NOSORT STOPKEY TABLE ACCESS BY INDEX ROWID, 20090506_rownum.MYTABLE INDEX FULL SCAN, 20090506_rownum.IX_MYTABLE_PAGINATOR_ID
rownum
SELECT * FROM ( SELECT t.*, ROWNUM AS rn FROM ( SELECT * FROM mytable ORDER BY paginator, id ) t ) WHERE rn BETWEEN 900001 AND 900010
| ID | PAGINATOR | VALUE | RN | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 900001 | 90 | Value 900001 | 900001 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 900002 | 90 | Value 900002 | 900002 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 900003 | 90 | Value 900003 | 900003 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 900004 | 90 | Value 900004 | 900004 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 900005 | 90 | Value 900005 | 900005 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 900006 | 90 | Value 900006 | 900006 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 900007 | 90 | Value 900007 | 900007 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 900008 | 90 | Value 900008 | 900008 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 900009 | 90 | Value 900009 | 900009 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 900010 | 90 | Value 900010 | 900010 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 10 rows fetched in 0.0005s (0.7058) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
SELECT STATEMENT VIEW COUNT VIEW TABLE ACCESS BY INDEX ROWID, 20090506_rownum.MYTABLE INDEX FULL SCAN, 20090506_rownum.IX_MYTABLE_PAGINATOR_ID
從上文中,可以看出,使用rownum的查詢速度略快於row_number函數。
然后再看一個row_number查詢,可以看出oracle足夠的智能,它可以通過使用聯合索引而避免進行排序操作,然后通過使用stopkey操作,可以直接快速查找到相應的數據信息。
rownum查詢也同樣使用索引,但並沒有利用stopkey條件,只是簡單的計數操作。
那么,能否同樣讓rownum使用stopkey呢。在之前的查詢中,oracle並不知道這個rn就是在內層查詢rownum的別名,我們可以重寫查詢,在外層查詢中使用rownum,這樣就可以在外層利用stopkey條件了。這就是我們常見的oracle3層分頁的變形:
SELECT * FROM ( SELECT t.*, ROWNUM AS rn FROM ( SELECT * FROM mytable ORDER BY paginator, id ) t ) WHERE rn >= 900001 AND rownum <= 10
| ID | PAGINATOR | VALUE | RN | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 900001 | 90 | Value 900001 | 900001 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 900002 | 90 | Value 900002 | 900002 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 900003 | 90 | Value 900003 | 900003 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 900004 | 90 | Value 900004 | 900004 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 900005 | 90 | Value 900005 | 900005 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 900006 | 90 | Value 900006 | 900006 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 900007 | 90 | Value 900007 | 900007 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 900008 | 90 | Value 900008 | 900008 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 900009 | 90 | Value 900009 | 900009 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 900010 | 90 | Value 900010 | 900010 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 10 rows fetched in 0.0005s (0.4714s) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
SELECT STATEMENT COUNT STOPKEY VIEW COUNT VIEW TABLE ACCESS BY INDEX ROWID, 20090506_rownum.MYTABLE INDEX FULL SCAN, 20090506_rownum.IX_MYTABLE_PAGINATOR_ID
在這個查詢中,oracle利用了stopkey,同時速度只有471ms,比原來更快。
如果row_number和rownum使用同樣的執行計划,但為什么rownum明顯更快呢。
這是因為:oracle的歷史實在是太久了,而不同的時間導致相同的特性卻有不同的效果。
rownum在oracle6中被引進,發布時間為1988年,在當時什么資源和條件都不滿足的情況下,作為一個簡單的計數器,被認為是非常簡單和高效的。
而隨着時代的發展,更多的需求被提及出來,這時,一個相當於但功能比rownum更強大的函數被引入,這就是row_number函數,它從oracle9i開始被引進。這時,效率已經不再是惟一的條件了,所以row_number的實現也不再以效率為惟一的指標了。
當然,如果你有更多的要求,如分組排序等,則需要使用row_number函數,但如果你僅僅是簡單的分頁查詢,建議使用rownum,這也是為什么在現在的時代rownum還是這么流行(據說在oracle12c中有offset分頁操作符了,內部同樣使用row_number函數,這樣rownum可以退休了)
以下是英文原文:http://explainextended.com/2009/05/06/oracle-row_number-vs-rownum/
