背景
首先感謝網友@aixuexi 在評論中的提醒,原博文介紹的幾種都不是最高效,現已修改加入另一種更高效的方法。
以前都是使用mysql和oracle,對sqlserver的使用不多。最近因項目原因,要讀取其他項目的數據庫,取出某個門的開關歷史記錄,而對方使用的是sqlserver,所以研究起了sqlserver的分頁,經過幾次實踐,慢慢的從低效的分頁寫到了高效的分頁。
表結構
history表
歷史記錄ID:id(唯一引索)
操作時間:time
開門或關門:flag
由誰操作:user_Id
屬於哪個設備:device_id
下面我們先介紹四種分頁的方法,以【一種低效】——【兩種較高效】——【一種高效】的順序進階的介紹,在最后再附上測試結果。
修改:經過網友@aixuexi 的提醒,加入最后一種【更高效】的方法介紹
低效的sql
思路:

最里層:先從history表根據時間倒序查出前50010條記錄
中間層:從以上的查詢結果中根據時間正序查出前10條記錄,一正一反剛好就拿出了第10000條到10010條記錄了。
最外層:根據時間倒序拿出以上的查詢結果
SQL代碼:
select * from ( select top 10 * from ( select top 50010 * from history order by time desc ) h order by h.time asc ) hh order by hh.time desc
經下面的檢驗,這種查詢效率比較低下。
其原因是因為每一層的查詢都使用了select * ,即掃描所有的這段,但是 “最里層” 和 “中間層” 根本就沒必要select * ,這兩層目的只是為了把最后一層的搜索范圍定位在第10000-10010條之間,所以,在這兩層里,我們只要拿出關鍵字ID和排序字段time就好。
較高效的SQL(1)————使用where ... =

最里層:先從history表根據時間倒序查出前50010條記錄,只拿出id和time
中間層:從以上的查詢結果中根據時間正序查出前10條記錄,只拿出id和time,一正一反剛好就拿出了第10000條到10010條記錄了。
最外層:根據時間倒序拿出以上的查詢結果,select *拿出所有字段,查詢范圍用where ... = ... 來匹配。
SQL代碼:
select * from history hh, ( select top 10 id ,time from ( select top 50010 id ,time from history order by time desc ) h order by h.time asc ) hhh where hhh.id = hh.id order by hhh.time desc
經檢驗,這種分頁 方法比上一種快一點點。
主要原因在與“最里層”和“中間層”的兩次查詢,都只是查出id和time,而不是select * ,從這個角度講提升了效率。但最后又用了where...=語法,比起上一種分頁方法,又降低了一點效率。但是where...=語法速度很快,所以總體上還是這種分頁方法更快一些。
較高效的分頁(2)————使用where...in

最里層:先從history表根據時間倒序查出前50010條記錄,只拿出id和time
中間層:從以上的查詢結果中根據時間正序查出前10條記錄,只拿出id和time,一正一反剛好就拿出了第10000條到10010條記錄了。
最外層:根據時間倒序拿出以上的查詢結果,select *拿出所有字段,查詢范圍用where...in ()來匹配
SQL代碼:
select * from history hh where id in ( select top 10 id from ( select top 50010 id ,time from history order by time desc ) h order by h.time asc ) order by hh.time des
這種分頁方法,與上一種分頁方法比起來,區別是這種使用了where...in(),而不是where...=,原理上差別不大,但可能是SQLServer內部優化的原因,使用where...in比使用where...=要快一些。具體在下面的計較表格可以看出。
高效的分頁————使用row_number() over

最里層:查詢出前50010條數據,只拿出ID字段,同時使用row_number() over 語法,增加一個n字段,代表該條數據時第幾行。
最外層:根據where...來匹配id,同時直接拿出 n>50000 的數據。
SQL代碼:
select hhh.n , hh.* from history hh , ( select top 50010 row_number() over ( order by time desc ) n,id from history ) hhh where hhh.id = hh.id and hhh.n > 50000 order by hhh.n desc
這種分頁方法,首先只是兩次查詢,這無非提高了效率。最里層查詢出來的“虛列”——n,sqlserver不知道會不會為其加上索引,個人認為會,但想不出什么驗證的方法。假如有加入索引的話,那在這個地方,使用 “n >某個數字” 的查詢方法,又比前幾次查詢快了一點。
更高效的分頁——row_number() over + 只查詢一次

最里層:查詢出前50010條數據,使用row_number() over 語法,增加一個n字段,代表該條數據時第幾行,同時查出我們想要的信息,這里跟其他方法一樣查詢出所有:*。
最外層:根據where...來匹配n,同時直接拿出 n>50000 且 n<50010 行的數據。
SQL代碼:
select * from ( select *,row_number() over ( order by time desc ) n from history ) hhh where hhh.n > 50000 and hhh.n <= 50010
幾種分頁方法速度比較
以下進行兩種測試,第一種是查詢出1000-1010條數據,第二種是查詢出第50000-50010條數據。記錄的秒數是查詢50次總共的用時,每組測試5次,最后取平均值。
| 【查詢1000-1010條數據】 | 第一次 | 第二次 | 第三次 | 第四次 | 第五次 | 平均 |
| 低效的分頁 | 6.344s | 5.687s | 5.797s | 5.704s | 5.641s | 5.835s |
| 較高效的分頁(1)——where...= | 4.485s | 5.281s | 5.094s | 5.281s | 5.313s | 5.091s(勝出) |
| 較高效的分頁(1)——where...in | 5.093s | 5.328s | 5.14s | 5.406s | 5.297s | 5.253s |
| 高效的分頁——row_number() over | 5.437s | 5.39s | 5.156s | 5.016s | 5.344s | 5.269 |
| 更高效的分頁——row_number() over + 只查詢一次 | 5.188s | 4.875s | 5.172s | 4.953s | 4.875s | 5.0126s(勝出) |
從中可以看出,在查詢的行數較少時,使用 【更高效的分頁——row_number() over + 只查詢一次】是最快的一種分頁方法。
| 【查詢50000-50010條數據】 | 第一次 | 第二次 | 第三次 | 第四次 | 第五次 | 平均 |
| 低效的分頁 | 10.844s | 9.985s | 10.172s | 10.0s | 10.297s | 10.260s |
| 較高效的分頁(1)——where...= | 9.625s | 9.469s | 9.14s | 9.171s | 9.219s | 9.325s |
| 較高效的分頁(1)——where...in | 9.156s | 9.61s | 9.187s | 9.218s | 9.219s | 9.278s |
| 高效的分頁——row_number() over | 7.844s | 6.765s | 6.422s | 7.359s | 6.875s | 7.05s(勝出) |
| 更高效的分頁——row_number() over + 只查詢一次 | 6.730s | 6.109s | 7.109s | 5.328s | 5.515s | 6.086s(勝出) |
從中可用看出,在查詢的行數較多時,使用【更高效的分頁——row_number() over + 只查詢一次】是最快的一種分頁方法,而且快了好幾個檔次!
