T-SQL筆記38_改善SQL分頁查詢效能_sp_cursoropen 和 sp_cursorfetch

 


之前有分享過分頁寫法
這次遇到其他種分頁作法


舊系統分頁查詢SP

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
Create  proc  [dbo].[SSP_GetPage]
(
	@sqlstr  nvarchar(4000), 
             	@pagepos  int=1,               
             	@pagesize  int=99999999,
	@recsize int output
)                  
           as  
begin  
	set  nocount  on  
           	declare  @P1  int ,@P2 int ,@P3 int,@reccount int
          	set  @pagepos=(@pagepos-1)*@pagesize+1
           	exec  sp_cursoropen  @P1 output , @sqlstr , 1  , 8193 , @recsize output 
	exec  sp_cursorfetch  @P1,16,@pagepos,@pagesize 
           	exec  sp_cursorclose  @P1 
end

這段程式碼定義了一個名為 SSP_GetPage 的存儲過程。該過程接受四個參數:

@sqlstr:一個字符串參數,用於指定將要執行的 SQL 陳述式。
@pagepos:一個整數參數,指定要檢索的記錄起始位置,默認為 1。
@pagesize:一個整數參數,指定要檢索的記錄數目,默認為 99999999。
@recsize:一個輸出參數,指定檢索到的記錄數目。
在存儲過程的主體中,首先使用 set nocount on 語句來禁用向客戶端返回計數信息的功能。
然後聲明了四個整型變量 @P1、@P2、@P3 和 @reccount。

接下來,根據 @pagepos 和 @pagesize 計算要檢索的記錄起始位置,然後執行 sp_cursoropen 系統存儲過程來打開游標,使用 sp_cursorfetch 函數檢索指定頁面的記錄,最後再使用 sp_cursorclose 函數關閉游標。



用到了幾個比較沒碰過的語法

sp_cursoropen,
sp_cursorfetch,
sp_cursorclosee



sp_cursoropen是一個系統預存程序,用於打開一個游標,將查詢結果集儲存在系統內存中,並返回一個參數以供後續操作。它的語法為:

sp_cursoropen語法參數

1
sp_cursoropen [@cursor RETURN] output, [@stmt] NVARCHAR(4000), [@options] INT, [@scrollopt] INT, [@ccopt] INT, [@scrollcursortype] INT


其中,
@cursor RETURN是一個輸出參數,用於返回一個游標句柄;
@stmt是一個 NVARCHAR(4000)類型的參數,表示要執行的 SQL 陳述式;
@options是一個 INT類型的參數,指定了游標的類型;
@scrollopt是一個 INT類型的參數,指定了游標滾動的選項;
@ccopt是一個 INT類型的參數,指定了游標的光標屬性;
@scrollcursortype是一個 INT類型的參數,指定了游標的滾動類型。


sp_cursorfetch是一個系統預存程序,用於檢索一個游標中的一個或多個行。它的語法為:
sp_cursorfetch語法參數

1
sp_cursorfetch [@cursor] INT, [@fetchtype] INT, [@row] INT, [@rows] INT, [@rowcount] INT output


其中,
@cursor是一個 INT類型的參數,表示要檢索的游標;
@fetchtype是一個 INT類型的參數,表示要檢索的類型;
@row是一個 INT類型的參數,表示要檢索的第一行的位置;
@rows是一個 INT類型的參數,表示要檢索的行數;
@rowcount是一個輸出參數,返回檢索到的行數。


sp_cursorclose是一個系統預存程序,用於關閉游標,釋放遊標相關的資源,包括內存、鎖定和其他系統資源。它的語法為:
sp_cursorclose語法參數

1
sp_cursorclose [@cursor] INT

其中,@cursor是一個 INT類型的參數,表示要關閉的游標。



底下是嘗試產生大量資料來模擬這支sp如何呼叫使用的範例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
-- 建立一個表來存儲資料
CREATE TABLE TestTable (
    ID INT IDENTITY(1,1) PRIMARY KEY,
    Name VARCHAR(50),
    Age INT
)

-- 插入 2000 萬筆資料
DECLARE @i INT = 1
WHILE (@i <= 20000000)
BEGIN
    INSERT INTO TestTable (Name, Age) VALUES ('User ' + CAST(@i AS VARCHAR(10)), @i % 100)
    SET @i = @i + 1
END

-- 呼叫 SSP_GetPage 存儲過程來檢索資料
DECLARE @recsize INT
EXEC SSP_GetPage 'SELECT * FROM TestTable', 1, 1000, @recsize OUTPUT

上面的例子會向 TestTable 表中插入 2000 萬筆資料,
然後調用 SSP_GetPage 存儲過程來檢索這些資料。
在呼叫存儲過程時,我們指定要檢索的記錄起始位置為 1,每頁檢索的記錄數目為 1000。@recsize 參數將被用來存儲檢索到的記錄數目。
注意,由於檢索的資料量很大,所以可能需要等待一段時間才能完成操作。

由於利用SQL的遊標存儲過程分頁效能會比較差
用游標的方式,只適合於資料量沒這麼大量的表,如果表在一萬行以上一些效能差異就很明顯
能感受到轉圈耗費的時間拉長。
sp_cursoropen 和 sp_cursorfetch 是 SQL Server 提供的用於處理查詢結果集的系統函數,它們都使用游標來處理結果集。游標是一種指標,可以讓程式在結果集中移動並操作數據。

sp_cursoropen 函數用於打開一個遊標,它接受多個參數,包括 SQL 語句、游標類型、游標並發控制方式、游標選項等。它會將查詢結果放入一個暫存表中,然後返回一個遊標的句柄,這個句柄可以用來進一步操作結果集。
sp_cursorfetch 函數用於從遊標中獲取數據,它接受多個參數,包括遊標句柄、擷取方式、起始位置、擷取行數等。它會根據參數指定的位置和數量擷取數據並返回,並且在擷取完成後自動將遊標移動到下一個位置,以便進一步擷取數據。

由於 sp_cursoropen 和 sp_cursorfetch 都使用游標來處理結果集,因此它們的效能較差,會占用大量資源。游標需要在內存中建立一個暫存表來存儲結果集,而且游標還需要保持與數據庫的連接狀態,這些都會導致效能降低。此外,使用游標還容易出現死鎖等問題,這些都會對程式的穩定性造成影響。


經改寫的版本

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
CREATE PROCEDURE [dbo].[SSP_GetPage2]
(
    @sqlstr nvarchar(max),
    @pagepos int = 1,
    @pagesize int = 99999999,
    @recsize int OUTPUT
)
AS
BEGIN
    SET NOCOUNT ON;

    DECLARE @offset INT = (@pagepos - 1) * @pagesize + 1;
    DECLARE @fetchSize INT = @pagesize;
    DECLARE @totalCount INT;

    -- Get total record count
    SET @sqlstr = N'SELECT @totalCount = COUNT(*) FROM (' + @sqlstr + ') AS [t]';
    EXEC sp_executesql @sqlstr, N'@totalCount INT OUTPUT', @totalCount OUTPUT;

    -- Calculate output parameters
    SET @recsize = @@ROWCOUNT;
    IF @recsize = 0
    BEGIN
        SET @pagepos = 0;
        SET @pagesize = 0;
    END;

    -- Retrieve data for specified page
    SET @sqlstr = CONCAT(
        'SELECT * FROM (',
        @sqlstr,
        ') AS [t4] ORDER BY (SELECT NULL) OFFSET @offset ROWS FETCH NEXT @fetchSize ROWS ONLY;'
    );

    EXEC sp_executesql @sqlstr, N'@offset INT, @fetchSize INT', @offset, @fetchSize;
END







留言

這個網誌中的熱門文章

何謂淨重(Net Weight)、皮重(Tare Weight)與毛重(Gross Weight)

Architecture(架構) 和 Framework(框架) 有何不同?_軟體設計前的事前規劃的藍圖概念

經得起原始碼資安弱點掃描的程式設計習慣培養(五)_Missing HSTS Header