曲演雜壇 蛋疼的ROW NUMBER函式

2021-09-06 16:58:28 字數 4104 閱讀 6293

使用row_number來分頁幾乎是家喻戶曉的東東了,而且這東西簡單易用,簡直就是程式設計師居家必備之殺器,然而row_number也不是一招吃遍天下鮮的無敵bug般存在,最近就遇到幾個小問題,拿出來供大家娛樂下。

問題1:為什麼加where條件就慢,不加反而快?

查詢sql:

with

tempas(

select*,

row_number()

over(order

by t2.c6 desc) as

ridfrom tb001 as

t1inner

join tb002 as

t2on t1.c1=

t2.c1

where t1.c2>

1000

and t2.c3<

99999

and t1.c4=5)

select

*from

temp

where rid between

0and

10

開發大哥很激動地問我,對上面類似的的查詢,如果沒有where rid between 0 and 10的話,查詢在1秒內完成,如果有where條件,執行超過30秒未結束,不帶where條件返回300行左右資料,where條件過濾後返回10行資料,返回的資料行長度較小,可以忽略由於返回資料大小對網路和顯示的影響,那問題出在那呢?稍微有點dba經驗的人都會很快找到問題根源--執行計畫不對。

讓我們換個簡單的sql來分析下

with

tempas(

select*,

row_number()

over(order

by t1.c1 desc) as

ridfrom tb001 as

t1where t1.c2>

1000

)select

*from

temp

where rid between

0and

10

讓我們揣測下上面查詢如何實現,假設在t1.c1有索引ix_c1,在t1.c2上有索引ix_c2。

實現方式1:

a=>針對cte內部的查詢,先利用索引ix_c2找出滿足條件t1.c2>1000的資料,得到結果集u1

b=>對結果集u1按t1.c1排序,計算出u1中每行rid列的值,得到結果集u2

c=>對結果集u2查詢滿足rid between 0 and 10過濾的行,得到結果集u3

d=>將結果集u3返回

實現方式2:

a=>利用索引ix_c1按order by t1.c1 desc來依次訪問t1資料

b=>檢查步驟a得到的行是否滿足t1.c2>1000條件,將滿足條件的結果放入結果集u1中,然後一次遞增rid

c=>檢查步驟b得到的結果集ui,當得到足夠資料行(rid between 0 and 10)後停止步驟a和b

d=>將結果集u1返回

以上兩種方式都能得到正確的返回結果,但是那種更好呢?

對於實現方式1,假設表t1有100w資料,如果滿足t1.c2>1000的行只有20行,那麼使用索引ix_c2快速找出滿足條件的20行資料,然後對這20行資料排序也只會消耗很輕微的cpu資源;但如果滿足t1.c2>1000的行只有99w行,那麼排序就消耗大量cpu資源,從而導致查詢慢。

對於實現方式2,假設表t1有100w資料,按照索引ix_c1 倒序遍歷c1的值,如果遍歷前50行便能查詢到滿足t1.c2>1000的10行資料,那麼查詢可以很快結束,只消耗少量的邏輯讀;但如果需要遍歷前99w資料才能找到滿足t1.c2>1000的10行資料,那麼就會消耗大量的邏輯讀,從而導致查詢慢。

由此,我們不難得出乙個結論:沒有絕對正確的執行計畫,只有相對高質量的執行計畫。

我們知道,在sql server生成執行計畫時,會根據輸入的引數和統計資訊去預估一些步驟的影響行數和開銷,尋找開銷較小的執行計畫,對於本篇開頭提到的查詢,sql server很容易受到rid between 0 and 10的**,選擇類似於實現方式2的的執行計畫,而資料分布情況又恰好是針對該方式最壞的情況,就出現了我們遇到的結果,查詢死慢死慢的。

類似的案例還有:

1. 查詢返回資料20行,然後在此查詢的基礎上增加order by 和top(10), 結果執行效率慢了很多,於是就產生了為什麼對20行資料排序取top會這麼慢的疑惑?

2. 查詢返回資料20行,在查詢中分別增加select top(20)和select top(10000),結果select top(10000)的比select top(20)快很多倍,我遇到的案例有select top(10000)在5ms內完成,然後select top(1)的十分鐘都沒有結果

以上案例都有相同的操作order by+top,row_number本質上也是order by+top,我們知道cpu資源是伺服器資源中最寶貴的資源,而對結果集排序又是乙個很耗cpu資源的過程,sql server為節省cpu資源選擇了乙個「它」認為比較合適的執行計畫,結果悲劇了。

針對哪位開發大哥的問題,我嘗試了各種寫法,在不動用臨時表和索引提示的情況下,我還真搞不定這sql,於是我來了個**小招數:

with

tempas(

select*,

row_number()

over(order

by t2.c6 desc) as

ridfrom tb001 as

t1inner

join tb002 as

t2on t1.c1=

t2.c1

where t1.c2>

1000

and t2.c3<

99999

and t1.c4=5)

select

*from

temp

where rid+

0between

0and

10

學術派們要開始叫囂了,這種rid+0 between 0 and 10寫法不科學啊,效率低下,初級程式設計師不懂sql寫的爛sql啊。。。

使用rid+0來騙過查詢優化器,讓「它」無法估算出between 0 and 10需要返回的行數,這樣「它」只能老老實實地「先」做cet內部的查詢.

ps: 我騙得過查詢優化器,騙不過開發大哥,他一直認為這個寫法太bt,問了其他的dba好幾次,就是不採納我的建議,悲催啊。

乙個小建議:

不要見到類似wheere c1+10>20這種的就叫囂不好,就喊著不能走索引的口號,看看場景再說麼,萬一c1上就壓根沒有索引呢?

row_number在實現分頁行的確很好用,但是也不是所有場景都適用,這是乙個真實的例子

乙個查詢只有兩個引數@p1和@p2,代表取第@p1行到第@p2行之間的數

當@p1=0 and @p2=1000時,消耗是這樣的:

表 '

***detail

'。掃瞄計數 186,邏輯讀取 4922 次,物理讀取 0 次,預讀 0 次,lob 邏輯讀取 0 次,lob 物理讀取 0 次,lob 預讀 0

次。表

'***

'。掃瞄計數 1,邏輯讀取 809 次,物理讀取 0 次,預讀 0 次,lob 邏輯讀取 0 次,lob 物理讀取 0 次,lob 預讀 0

次。 cpu 時間

=0 毫秒,占用時間 =

73 毫秒。

當@p1=7241284 and @p2=7240285時,消耗是這樣的:

表 '

***detail

'。掃瞄計數 1468817,邏輯讀取 35838994 次,物理讀取 1 次,預讀 0 次,lob 邏輯讀取 0 次,lob 物理讀取 0 次,lob 預讀 0

次。表

'***

'。掃瞄計數 1,邏輯讀取 5983509 次,物理讀取 0 次,預讀 0 次,lob 邏輯讀取 0 次,lob 物理讀取 0 次,lob 預讀 0

次。 cpu 時間

=45926 毫秒,占用時間 =

56816 毫秒。

真有份這麼多頁的,無語吧!!!

既然無語,我就不多做解釋,說多就是眼淚,看看就好。

打完收工,妹子附上

曲演雜壇 頁拆分

以下測試基於版本 sql server 2008 很多同行會問起頁拆分的相關的問題,自己對頁拆分頁迷迷糊糊,有點雲裡霧裡的感覺,今天來測試測試。首先生成測試資料 使用testdb資料庫來測試 usetestdb godrop table tb01 go 建立測試表tb01 create table ...

蛋疼的遞迴

幾個經典的遞迴場景 1.斐波那契 2.二叉樹的周遊 深度 前中後序 3.全排列問題 非簡單的全排列 允許字母重複 4.尋找滿足條件的n個數 第乙個的變種 跳台階 題目 乙個台階總共有n 級,如果一次可以跳1 級,也可以跳 2 級。求總共有多少總跳法,並分析演算法的時間複雜度。第二個要複習下非遞迴的寫...

曲苑雜壇 收縮資料庫檔案

很多人在刪除大量資料後收縮資料庫,卻發現沒法收縮到預期效果。由於使用dbcc shrinkfile來收縮資料檔案時,是針對資料區來收縮,因此可以先使用dbcc showfilestats來檢視檔案中未使用的分割槽數 totalextents usedextents 如果刪除大量資料但未使用分割槽數比...