排序分頁,排名次在咱們系統中應用的非常多,當表的數據量大的時候,排序分頁,排名次就成爲了SQL性能的瓶頸,生產庫對這類SQL多有預警!
最近對海量數據排序分頁,排名次做了深入研究,終於找到一個很好的方法,表的數據量對這種方法的性能影響不大,只與記錄所在的頁數有關,頁數越大,性能稍有降低,第一個分頁最快。
對數據量爲10000000的表在開發庫上做了測試,每頁顯示20條記錄,第1000頁用了0.187秒,用我們平常用的方法執行了125.238秒 ,比較了下結果和順序都是一樣的。
新方法sql如下:
SELECT/*+ leading(t1) use_nl(t1 t2) */
t1.rn, --名次
t2.*
FROM kfj_posters_info t2,
(SELECT/*+ index(t IDX_KFJ_POSTERS_INFO_NUM_DATE)*/
rownum rn,
ROWID rd
FROM kfj_posters_info t
WHERE t.posters_url ISNOTNULL
AND rownum <= 20000
ORDER BYpseters_vote_num DESC,
date_created DESC) t1
WHERE t2.rowid= t1.rd
AND t1.rn > 19980
ORDER BY t1.rn ASC;
說下這個sql的思路:
1, 通過pseters_vote_num,date_created兩個列的降序索引IDX_KFJ_POSTERS_INFO_NUM_DATE取出當前頁所有記錄的 rowid (因爲索引中已排好序,這一步非常快);
2, 通過rowid 取出當前頁表的數據(通過rowid 訪問表是最快的方法) ;
3, 爲了保證結果正確,最後再對當前頁的數據做一次排序。
4, 爲了保證執行計劃的正確,這個sql的兩個hint是必須要加的。如果執行計劃不正確可能導致結果不對或很慢。
新方法的優缺點:
優點:性能很好,只對當前頁的記錄排序,消耗的cpu,I/O,內存資源很少
缺點:依賴索引,對執行要求很高,如果執行計劃不正確,可能影響結果的正確性,所以必須使用hint去限制sql走正確的執行計劃。
常規方法如下
SELECT *
FROM (SELECTrownum rn,
t1.*
FROM (SELECT *
FROM kfj_posters_info t
WHERE t.posters_url ISNOTNULL
ORDER BY pseters_vote_num DESC,
date_created DESC) t1
WHERE rownum <= 20000
) t2
WHERE t2.rn> 19980
常規方法的優缺點:
優點:執行計劃的正確與否不影響結果
缺點:性能很差。對太多沒有必要的數據做order by ,每查一個分頁都要對全表做order by,消耗太多cpu,I/O,內存資源 。
=》 如果大家需要對大數據量做排序分頁,排名次操作,請嘗試新方法,謝謝!
另外介紹兩個可以排名次的函數
dense_rank() over() -- 數值相同的算做一個名次,下個數值的名次直接加 1
rank() over() --數值相同的算做一個名次,下個數值的名次不是加1,
具體區別大家通過下面sql在開發環境運行下就可以明白了
SELECT/*+ leading(t1) use_nl(t1 t2) */
t1.rn,
t1.dense_rank_, -- dense_rank() over(ORDER BY t.pseters_vote_num DESC)
t1.rank_, -- rank() over(ORDER BY t.pseters_vote_num DESC)
t2.pseters_vote_num,
t2.date_created,
t2.*
FROM kfj_posters_info t2,
(
SELECT/*+ index(t IDX_KFJ_POSTERS_INFO_NUM_DATE)*/
dense_rank() over(ORDERBY t.pseters_vote_num DESC) dense_rank_ ,
rank() over(ORDERBYt.pseters_vote_num DESC) rank_,
rownum rn,
ROWID rd
FROM kfj_posters_info t
WHERE t.posters_urlISNOTNULL
AND rownum <= 20
ORDER BYpseters_vote_num DESC
) t1
WHERE t2.rowid= t1.rd
AND t1.rn > 0
ORDER BY rn ASC;
結果如下: