首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 數據庫知識 >  >> SQL Server 資料庫 >> 關於SqlServer

SQL Server 分頁存儲過程效能大比拚:ROW_NUMBER() vs 傳統 NOT IN — 50 萬筆實測

     編輯:關於SqlServer

測試環境與資料

  • 硬體:Core 2 Duo 2.20GHz / 1GB RAM
  • DB:SQL Server 2008
  • 資料表 Xtest:ID BIGINT IDENTITY PK、XName NVARCHAR(50)、XDemo NVARCHAR(500)、XName 有非叢集索引
  • 資料量:**50 萬筆**
  • 查詢條件:XName LIKE '%222name%' (低選擇性、前導萬用字元,索引無效,全表掃描)

兩種分頁存儲過程實作

A. 傳統 SQL 2000 風格:NOT IN + TOP (sp_Pager2000)


-- 核心邏輯
IF @PageIndex > 1
    SET @sql = 'SELECT TOP ' + @PageSize + ' * FROM ' + @tblName + 
               ' WHERE ' + @PKName + ' NOT IN (SELECT TOP ' + @PageSize*(@PageIndex-1) + ' ' + @PKName + ' FROM ' + @tblName + ' ' + @strWhere + ' ' + @strOrder + ') ' + @strWhere;
ELSE
    SET @sql = 'SELECT TOP ' + @PageSize + ' * FROM ' + @tblName + ' ' + @strWhere;
-- 執行動態 SQL

B. SQL 2005+ 標準:ROW_NUMBER() OVER (sp_Pager2005)


SET @strSQL = 'SELECT * FROM (SELECT ROW_NUMBER() OVER (' + @strOrder + ') AS ROWID, ' + @strGetFields + ' FROM [' + @tblName + '] ' + @strWhere + ') AS sp WHERE ROWID BETWEEN ' + @StartRow + ' AND ' + @EndRow;

實測結果 (單位:毫秒)

方案統計總筆數策略第 1 頁第 2 頁第 3 頁第 4 頁第 5 頁第 6 頁
NOT IN (傳統)每頁統計293731403453360938434156
NOT IN (傳統)僅首頁統計293734359381210461281
ROW_NUMBER()每頁統計293730313156329634213515
ROW_NUMBER()僅首頁統計2937218359468578687

深度分析

1. 統計總筆數 (COUNT(*)) 是最大殺手

  • 全表掃描 + LIKE '%...%' 過濾,**每次 2.9 秒**。
  • **最佳化**:首頁取得總筆數後,**快取起來** (Session、Redis、OutputCache),後續分頁**完全不再統計**。效能提升 **10-15 倍**。

2. ROW_NUMBER() 完勝 NOT IN (在「僅首頁統計」模式下)

  • 第 2 頁:218ms vs 343ms (快 36%)
  • 第 6 頁:687ms vs 1281ms (快 46%)
  • **原因**:ROW_NUMBER() 由優化器單一掃描 + 排序 + 區段篩選;NOT IN 子查詢隨頁碼增長,TOP N 掃描量線性增大 (第 6 頁掃前 6×PageSize 列),導致遞增開銷。

3. 每頁統計時兩者差異不大

因 COUNT(*) 佔 90%+ 耗時,分頁邏輯本身差異被掩蓋。

現代最佳分頁寫法 (SQL Server 2012+)


CREATE PROCEDURE dbo.sp_Pager_Modern
    @PageIndex INT = 1,
    @PageSize  INT = 20,
    @TotalRows INT OUTPUT
AS
BEGIN
    SET NOCOUNT ON;
    
    -- 1. 統計總筆數 (可選快取)
    SELECT @TotalRows = COUNT(*) FROM dbo.Xtest WHERE XName LIKE '%222name%';
    
    -- 2. OFFSET/FETCH 標準語法 (最佳化器支援最好)
    SELECT ID, XName, XDemo
    FROM dbo.Xtest
    WHERE XName LIKE '%222name%'
    ORDER BY ID
    OFFSET (@PageIndex - 1) * @PageSize ROWS
    FETCH NEXT @PageSize ROWS ONLY;
END;
  • OFFSET/FETCH 內部實作同 ROW_NUMBER(),但語法標準、可讀性高、優化器持續改進。
  • **Keyset Pagination (游標分頁)**:若翻頁極深 (第 10000 頁),改用 WHERE ID > @LastSeenID ORDER BY ID OFFSET 0 ROWS FETCH NEXT 20 ROWS ONLY,避免掃描前 N 萬列。

索引策略對分頁的影響

索引狀態ORDER BY 欄位WHERE 選擇性推薦方案
叢集索引鍵同叢集鍵高OFFSET/FETCH 极快 (有序掃描)
非叢集索引覆蓋同索引鍵高Index Seek + Key Lookup (或覆蓋索引無 Lookup)
無支援索引任意低 (LIKE %...%)全表掃描 + ROW_NUMBER/OFFSET,**必快取總筆數**
深分頁 (頁碼 > 1000)叢集鍵任意Keyset Pagination (WHERE ID > @LastID)

總結

  1. ROW_NUMBER() / OFFSET-FETCH > NOT IN:單掃描、無遞增掃描、優化器友善。
  2. 快取總筆數是銀彈:分頁查詢 90% 時間花在 COUNT(*),首頁統計後快取,後續頁亞秒級響應。
  3. 深分頁用 Keyset:避免 OFFSET 1000000 掃描百萬列丟棄。
  4. 索引設計優於語法選擇:覆蓋索引 + 叢集鍵排序 = 最快分頁。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved