淺談五種SqlServer存儲過程分頁方式
基礎數據
--定義表結構
CREATE TABLE [dbo].[Users](
[ID] [INT] IDENTITY(1,1) NOT NULL,
[NAME] [NVARCHAR](50) NOT NULL,
CONSTRAINT [PK_TEST] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[Users] ADD CONSTRAINT [DF_TEST_NAME] DEFAULT ('') FOR [NAME]
GO
--插入100w條基礎數據
DECLARE @i INT = 1
WHILE(@i <= 1000000)
BEGIN
INSERT INTO dbo.Users
(
NAME
)
VALUES
(
N'Random' + CAST(@i AS NVARCHAR(20)) -- NAME - nvarchar(20)
)
SET @i += 1;
END
GO
方式
方式一:利用 Select Top 和 Select Not In
CREATE PROCEDURE PROC_TopWithNotIn
@pageSize INT, --頁記錄數量
@pageIndex INT --當前頁
AS
SET NOCOUNT ON;
BEGIN
DECLARE @timediff DATETIME = GETDATE(),
@sql NVARCHAR(2000)
SET @sql = N'Select Top '+ STR(@pageSize) +' *
From Users
Where(
ID Not In(
Select Top '+ STR(@pageSize*(@pageIndex-1)) +' ID
From Users
Order By ID))
Order By ID';
EXEC(@sql);
SELECT DATEDIFF(MICROSECOND,@timediff,GETDATE()) AS 耗時;
END
方式二:利用 Select Top 和 Max(列鍵) 函數
CREATE PROCEDURE PROC_TopWithMax
@pageSize INT, --頁記錄數量
@pageIndex INT --當前頁
AS
SET NOCOUNT ON;
BEGIN
DECLARE @timediff DATETIME = GETDATE(),
@sql NVARCHAR(2000)
SET @sql = N'Select Top ' + STR(@pageSize) + ' *
From Users
Where ID > (
Select IsNull(Max(ID),0)
From (
Select Top ' + STR(@pageSize*(@pageIndex - 1)) + ' ID
From Users
Order By ID
)As t
)Order By ID';
EXEC(@sql);
SELECT DATEDIFF(MICROSECOND,@timediff,GETDATE()) AS 耗時;
END
方式三:利用 Select Top 和 中間變量
CREATE PROCEDURE PROC_TopWithMidvar
@pageSize INT, --頁記錄數量
@pageIndex INT --當前頁
AS
SET NOCOUNT ON;
BEGIN
DECLARE @timediff DATETIME = GETDATE(),
@midvar INT = 0,
@id INT = 0,
@sql NVARCHAR(2000)
SELECT @midvar = @midvar + 1,
@id = CASE WHEN @midvar <= @pageSize * (@pageIndex - 1) THEN ID
ELSE @id END FROM Users ORDER BY ID
SET @sql = N'Select Top '+STR(@pageSize)+' *
From Users Where ID > '+STR(@id)
EXEC(@sql);
SELECT DATEDIFF(MICROSECOND,@timediff,GETDATE()) AS 耗時;
END
方式四:利用 Row_Number()
--適用於SqlServer2005及以上版本
CREATE PROCEDURE PROC_RowNumber
@pageSize INT, --頁記錄數量
@pageIndex INT --當前頁
AS
SET NOCOUNT ON;
BEGIN
DECLARE @timediff DATETIME = GETDATE()
DECLARE @startNum INT,
@endNum INT;
SET @startNum = @pageSize * (@pageIndex - 1) + 1;
SET @endNum = @pageSize * @pageIndex;
SELECT * FROM
(
SELECT ROW_NUMBER() OVER(ORDER BY ID) AS num,*
FROM Users
)AS t
WHERE t.num BETWEEN LTRIM(STR(@startNum)) AND LTRIM(STR(@endNum));
SELECT DATEDIFF(MICROSECOND,@timediff,GETDATE()) AS 耗時;
END
方式五:利用 CTE 和 Row_Number()
CREATE PROCEDURE PROC_CTEWithRowNumber
@pageSize INT, --頁記錄數量
@pageIndex INT --當前頁
AS
SET NOCOUNT ON;
BEGIN
DECLARE @timediff DATETIME = GETDATE(),
@sql NVARCHAR(2000)
SET @sql = N'With cteTable
As (
Select
Ceiling((Row_Number() Over(Order By ID) - 1)/'+STR(@pageSize)+') as num,*
From Users
) Select * From cteTable Where num = ' + STR(@pageIndex - 1);
EXEC(@sql);
SELECT DATEDIFF(MICROSECOND,@timediff,GETDATE()) AS 耗時;
END
執行測試
針對數據進行分段測試,測試結果如下:
1、pageSize = 20 pageIndex = 1
2、pageSize = 20 pageIndex = 500
3、pageSize = 20 pageIndex = 5000
4、pageSize = 20 pageIndex = 50000
統計結果
測試/方式 | 方式一 | 方式二 | 方式三 | 方式四 | 方式五 | 排名最優 |
---|---|---|---|---|---|---|
1 | 3000 | 0 | 216000 | 0 | 220000 | 方式二/方式四 |
2 | 50000 | 78000 | 180000 | 50000 | 216000 | 方式一/方式四 |
3 | 63000 | 53000 | 180000 | 80000 | 173000 | 方式二 |
4 | 320000 | 283000 | 260000 | 676000 | 273000 | 方式三 |
總結:
1、五種分頁方式當數據位置越往後偏移,執行耗時越長,方式一、二、四表現明顯。
2、當數據量小的時候(百萬級以下)推薦使用方式一、二、四
3、當數據量大的時候(百萬級以上)推薦使用方式三
參考資料
五種SQL Server分頁存儲過程的方法及性能比較
SqlServer存儲過程分頁模板
Digression
本文博主只是針對百萬級數據的查詢效率做了測試,如果有朋友有更好的分頁方式或者測試結果歡迎私信呦~ 一起學習✊