曲演雜壇--當ROW_NUMBER遇到TOP


值班期間研發同事打來電話,說應用有超時,上服務器上檢查發現有SQL大批量地執行,該SQL消耗IO資源較多,導致服務器存在IO瓶頸,細看SQL,發現自己都被整蒙了,不知道這SQL是要干啥,處理完問題趕緊研究下。

SQL類似於:

WITH T1 AS 
(
    SELECT TOP ( 100 )
            ID ,
            ROW_NUMBER() OVER ( ORDER BY C1 ) AS RID
    FROM     [dbo].[TB002]
)
SELECT *
FROM   T1
WHERE  T1.RID > (1-1)*2147483647
    AND T1.RID < 1*2147483647

第一趕腳是寫這代碼的研發同事想分頁,但是這每頁的數據量有點嚇人啊(是我太膽小么?)

再仔細看下,趕腳又不是分頁,上面還有TOP(100)呢?

如果把TOP(100) 放到CTE外面,很容易理解,根據RID列過濾完后再取前100行數據。

對於上面的TOP(100) 在CTE內部SQL執行步驟如下

1>對表TB002中C1列排序計算每行的RID值,得到臨時結果集T1

2>對臨時結果集T1中數據“隨機”取100條(注意:因為CTE中TOP(100) 沒有對應ORDER BY 子句,因此無法保證返回的100條數據是有序的,即使在不少場景下返回的數據是按RID排序的) 得到臨時結果集T2

3>將臨時結果集T2的數據按照T1.RID > (1-1)*2147483647 AND T1.RID < 1*2147483647 的條件過濾,得到最終結果集T3

4>強最終結果集T3返回給客戶端

--=========================華麗分割線=======================================--

在SQL SERVER 世界里,ROW_NUMBER函數已經有些泛濫成災,很多不明真相的群眾磕着瓜子就把ROW_NUMBER函數寫到應用查詢中,甚至不少研發同事(抱歉有些人躺槍了)把ROW_NUMBER函數用到登峰造極的程度,當看到一條SQL里使用到N多ROW_NUMBER函數和子查詢再加N多大表關聯查詢,我都對自己DBA的身份表示懷疑,完全看不懂啊!!!

--=========================華麗分割線=======================================--

回歸正題,ROW_NUMBER函數的引入是為了更簡單地實現分頁,SQL SERVER 查詢引擎會將CTE外部的條件引入到CET內部,以避免CTE內部語句執行時訪問“無用”數據,如對下面的語句

;WITH T1 AS 
(
   
    SELECT  ID ,
            ROW_NUMBER() OVER ( ORDER BY ID ) AS RID
    FROM     [dbo].[TB002]
)
SELECT *
FROM   T1
WHERE  T1.RID > 10
    AND T1.RID < 30

由於表TB002上ID有索引,因此查詢會利用索引訪問前30條記錄,丟棄不滿足RID>10的第1到10條數據。

由於這種優化的存在,使得查詢無需先執行

SELECT  ID ,ROW_NUMBER() OVER ( ORDER BY ID ) AS RID FROM  [dbo].[TB002]

然后再執行WHERE  T1.RID > 10 AND T1.RID < 30 的過濾操作。

 

但如果CTE內部加入TOP子句,就使得CTE外部的T1.RID > 10 AND T1.RID < 30條件不能引入到CET內部(查詢優化器首先得保障返回結果集的正確性,然后才考慮執行的高效性)。對於研發同事也一樣,他們首先關注查詢結果是否正確,然后才考慮查詢效率是否高效,那么引入TOP是否能保證數據正確呢?

為了掩飾,我們將查詢做輕微調整如下:

;WITH T1 AS 
(
    SELECT TOP(10) ID ,
            ROW_NUMBER() OVER ( ORDER BY ID ) AS RID
    FROM     [dbo].[TB002]
)
SELECT *
FROM   T1
WHERE  T1.RID > 10
AND T1.RID < 30

我們會悲哀地發現,查詢返回結果為空,這顯然不是一個好兆頭,為什么會返回空呢?

輕輕推敲一下,我們就會發現,CTE內部的執行結果總是“巧合”地返回RID為1到10的數據,而外部條件RID>10又將這10條數據過濾掉,SO返回為空。

PS: 查詢優化器真的是“順手”返回前10條數據,因為恰好這10條數據“在手邊”,不能保證其他場景下也是返回RID為1到10的數據,當然也不是查詢優化器故意“坑人”哈

--=========================華麗分割線=======================================--

至此,我總算明白為啥要將寫SQL的那位兄弟要傳入入2147483647 這么大一個頁數量,估計是傳小了查不出數據,所以一勞永逸傳個最大值,想想也是醉了!

--=========================華麗分割線=======================================

總結:

編寫SQL的目的在於實現業務需求,而不是顯示個人SQL能力,也沒有“一招鮮吃遍天”可以秒殺所有問題的寫法,在尊重業務需求的前提下,依據業務場景,考慮數據分布和當前以及未來的數據量,用盡可能簡單的SQL地實現業務需求才是王道。

 

其實寫博客的目的是發圖,你們懂的!

圖片來源網絡,勿求粽子!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM