在編寫SQL批處理或存儲過程代碼的過程中,經常會碰到有些業務邏輯的處理,需要對滿足條件的數據記錄逐行進行處理,這個時候,大家首先想到的方案大部分是用“游標”進行處理。
舉個例子,在訂單管理系統中,客服需要對訂單日期為2012-09-01的銷售訂單進行某個批量操作,比如批量發貨操作,后台業務邏輯處理時,需要對滿足條件的訂單記錄進行逐行處理。
我首先是采用“游標”編寫的業務邏輯存儲過程,SQL代碼可以如下:

1 DECLARE @ORDERID VARCHAR(30) 2 3 -- 聲明局部游標:從訂單數據表獲取訂單日期為2012-09-01,訂單類型為Sales的訂單編號 4 DECLARE CURSOR_ORDER CURSOR LOCAL FOR 5 SELECT ORDERID FROM ORDERHD H WHERE ORDERDATE = '2012-09-01' AND H.ORDERTYPE = 'Sales' 6 7 -- 打開游標 8 OPEN CURSOR_ORDER 9 FETCH NEXT FROM CURSOR_ORDER INTO @ORDERID 10 WHILE @@FETCH_STATUS = 0 11 BEGIN 12 13 /* 14 此處編寫對當前行數據的業務邏輯處理代碼 15 */ 16 17 -- 得到下一條記錄 18 FETCH NEXT FROM CURSOR_ORDER INTO @ORDERID 19 END 20 21 -- 關閉游標 22 CLOSE CURSOR_ORDER 23 -- 釋放游標 24 DEALLOCATE CURSOR_ORDER
功能是實現了,但是客服在實際使用過程中,經常反饋批量操作效率太慢,需要等待較長時間才能完成操作。經過測試發現,速度慢在游標逐行處理過程中,當需要處理的記錄數較大,而且游標處理位於數據庫事務內時,速度非常慢。
那么,有什么方法可以解決這個處理速度慢的問題嗎?
經不斷的嘗試,終於找到一個方法,那就是用WHILE循環來進行逐行數據處理。首先將需要處理的數據記錄獲取到一個臨時表(此臨時表包括2個重要字段:REFID - 記錄行號,DealFlg:行處理標識,用1/0標識行是否已處理),然后WHILE循環對臨時表進行逐行處理,SQL代碼如下:

1 DECLARE @REFID INT 2 , @ORDERID VARCHAR(30) 3 4 -- 獲取待處理的數據記錄到臨時表 5 -- 字段說明:REFID:記錄行號 / DealFlg:行處理標識 6 SELECT REFID = IDENTITY(INT , 1, 1), DealFlg = 0, ORDERID 7 INTO #Temp_Lists 8 FROM ORDERHD 9 WHERE ORDERDATE = '2012-09-01' AND H.ORDERTYPE = 'Sales' 10 11 -- 獲取臨時表數據的最小行號 12 SELECT @REFID = MIN(REFID) FROM #Temp_Lists WHERE DealFlg = 0 13 14 -- 若最小行號不為空(有需要處理的數據) 15 WHILE @REFID IS NOT NULL 16 BEGIN 17 18 -- 獲取當前處理行的信息 19 SELECT @ORDERID = ORDERID FROM #Temp_Lists WHERE REFID = @REFID 20 21 /* 22 此處編寫對當前行數據的業務邏輯處理代碼 23 */ 24 25 -- 標識當前行已處理完畢 26 UPDATE #Temp_Lists SET DealFlg = 1 WHERE REFID = @REFID 27 28 -- 選擇下一行號 29 SELECT @REFID = MIN(REFID) FROM #Temp_Lists WHERE DealFlg = 0 AND REFID > @REFID 30 31 END
經過這樣對原存儲過程進行修正后,批量操作速度得到顯著提升。
有興趣的朋友,可以嘗試使用這個方法替代游標,對比2種方案的處理效率。