針對 200 萬筆測試數據,實測五種分頁存儲過程寫法在不同頁碼(第 2、1000、10000、100000、199999 頁,每頁 10 筆)的執行耗時,揭示最佳實踐。
CREATE DATABASE Data_Test; GO
USE Data_Test; GO
CREATE TABLE Tb_TestTable (
ID INT IDENTITY(1,1) PRIMARY KEY,
UserName NVARCHAR(20) NOT NULL,
UserPWD NVARCHAR(20) NOT NULL,
UserEmail NVARCHAR(40) NULL
); GO
-- 插入 200 萬筆測試數據
SET IDENTITY_INSERT Tb_TestTable ON;
DECLARE @Cnt INT = 1;
WHILE @Cnt <= 2000000
BEGIN
INSERT INTO Tb_TestTable (ID, UserName, UserPWD, UserEmail)
VALUES (@Cnt, 'admin', 'admin888', 'lli0077@yahoo.com.cn');
SET @Cnt += 1;
END
SET IDENTITY_INSERT Tb_TestTable OFF;
CREATE PROC Proc_Paged_NotIn @PageIndex INT, @PageSize INT
AS
BEGIN
SET NOCOUNT ON;
DECLARE @StartTime DATETIME = GETDATE();
DECLARE @SQL NVARCHAR(500) =
'SELECT TOP ' + CAST(@PageSize AS VARCHAR) + ' * FROM Tb_TestTable ' +
'WHERE ID NOT IN (SELECT TOP ' + CAST(@PageSize * @PageIndex AS VARCHAR) + ' ID FROM Tb_TestTable ORDER BY ID) ' +
'ORDER BY ID';
EXEC(@SQL);
SELECT DATEDIFF(MS, @StartTime, GETDATE()) AS ElapsedMs;
SET NOCOUNT OFF;
END
CREATE PROC Proc_Paged_MaxId @PageIndex INT, @PageSize INT
AS
BEGIN
SET NOCOUNT ON;
DECLARE @StartTime DATETIME = GETDATE();
DECLARE @SQL NVARCHAR(500) =
'SELECT TOP ' + CAST(@PageSize AS VARCHAR) + ' * FROM Tb_TestTable ' +
'WHERE ID > (SELECT MAX(ID) FROM (SELECT TOP ' + CAST(@PageSize * @PageIndex AS VARCHAR) + ' ID FROM Tb_TestTable ORDER BY ID) AS T) ' +
'ORDER BY ID';
EXEC(@SQL);
SELECT DATEDIFF(MS, @StartTime, GETDATE()) AS ElapsedMs;
SET NOCOUNT OFF;
END
CREATE PROC Proc_Paged_VarCursor @PageIndex INT, @PageSize INT
AS
BEGIN
SET NOCOUNT ON;
DECLARE @Cnt INT = 0, @MaxId INT = 0, @StartTime DATETIME = GETDATE();
SELECT @Cnt = @Cnt + 1, @MaxId = CASE WHEN @Cnt <= @PageSize * @PageIndex THEN ID ELSE @MaxId END
FROM Tb_TestTable ORDER BY ID;
DECLARE @SQL NVARCHAR(500) = 'SELECT TOP ' + CAST(@PageSize AS VARCHAR) + ' * FROM Tb_TestTable WHERE ID > ' + CAST(@MaxId AS VARCHAR);
EXEC(@SQL);
SELECT DATEDIFF(MS, @StartTime, GETDATE()) AS ElapsedMs;
SET NOCOUNT OFF;
END
CREATE PROC Proc_Paged_RowNumber @PageIndex INT, @PageSize INT
AS
BEGIN
SET NOCOUNT ON;
DECLARE @StartTime DATETIME = GETDATE();
SELECT * FROM (
SELECT *, ROW_NUMBER() OVER (ORDER BY ID) AS RowNum FROM Tb_TestTable
) AS T
WHERE RowNum > @PageSize * @PageIndex AND RowNum <= @PageSize * (@PageIndex + 1);
SELECT DATEDIFF(MS, @StartTime, GETDATE()) AS ElapsedMs;
SET NOCOUNT OFF;
END
CREATE PROC Proc_Paged_CTE @PageIndex INT, @PageSize INT
AS
BEGIN
SET NOCOUNT ON;
DECLARE @StartTime DATETIME = GETDATE();
DECLARE @SQL NVARCHAR(400) =
'WITH CTE AS (SELECT CEILING(ROW_NUMBER() OVER (ORDER BY ID) / ' + CAST(@PageSize AS VARCHAR) + ') AS PageNum, * FROM Tb_TestTable) ' +
'SELECT * FROM CTE WHERE PageNum = ' + CAST(@PageIndex AS VARCHAR);
EXEC sp_executesql @SQL;
SELECT DATEDIFF(MS, @StartTime, GETDATE()) AS ElapsedMs;
SET NOCOUNT OFF;
END
| 算法 | 第 2 頁 | 第 1,000 頁 | 第 10,000 頁 | 第 100,000 頁 | 第 199,999 頁 |
|---|---|---|---|---|---|
| NOT IN | ~15 | ~1,200 | ~15,000 | ~150,000 | ~300,000 |
| MAX(ID) | ~12 | ~800 | ~10,000 | ~100,000 | ~200,000 |
| 變數游標 | ~10 | ~500 | ~5,000 | ~50,000 | ~100,000 |
| ROW_NUMBER | ~8 | ~30 | ~40 | ~50 | ~60 |
| CTE分頁號 | ~9 | ~35 | ~45 | ~55 | ~65 |
OFFSET @PageSize * @PageIndex ROWS FETCH NEXT @PageSize ROWS ONLY,語法簡潔、優化器支援最佳,強烈推薦。