Sql-Server-2017

對於游標內的每次迭代,是否可以獨立地發生回滾/送出?

  • February 11, 2020

我們有這個會計解決方案生態系統,基本上,它有兩張表:

dbo.AccountFluxRecord
(
  Id,
  TotalCredits,
  TotalDebits,
  CreditMinusDebit,
  Application (the application used to insert the record)
)

dbo.AccountFluxRecordDetail
(
  Id,
  AccountFluxId,       (FK to above table)
  Type,                (Credit or Debit)
  Value,
  CreditBankAccountId, (nullable FK)
  DebtBankAccountId    (nullable FK)
)

我們有一些應用程序使用它,並且都使用儲存過程來插入新記錄。

SP_InsertAccountFluxRecord

我們將列表作業系統詳細資訊作為自定義使用者表類型傳遞給 SP。它進行了大量驗證,以確保所有記錄至少有一個貸方和一個借方,並且貸方減去債務的總和為零。它還驗證 BankAccountId 是否對每種操作都有效。

幾年來,它在數百名客戶中完美執行,每週插入數百萬條記錄。我們為大多數客戶託管數據庫,但其中一些堅持他們的數據必須在內部託管。我們可以接受,因為我們可以獲得一個開放的維護渠道。

今天,我們遇到了其中一位客戶自託管數據庫的問題。

我們發現了十幾個“壞”記錄。這些記錄缺少債務詳細資訊,這是不可能的,因為該特定應用程序從一對借記/貸記詳細資訊創建了一個 AccountFluxRecord,其中一個是另一個的鏡像。如果沒有一對有效的銀行賬戶,它甚至無法接受使用者輸入,但我們發現在這些不良記錄中 DebtBankAccountId 欄位為空。

我們保留所有操作的日誌,日誌反映了錯誤記錄按原樣插入,沒有刪除或更新。Obs:我們確實使用快照事務模式來避免我們過去遇到的一些死鎖問題。

TLDR 我得到了十幾個無效記錄,由 SP 插入,以阻止插入無效記錄,就好像繞過了所有應用程序和儲存過程驗證一樣。下面有一個片段顯示瞭如何在 SP 中處理驗證。

IF (@SumDetailDebit <> @SumDetailCredit) 
BEGIN
   set @Mesage = @ErrorPrefix + N'TOTAL DEBIT IS DIFFERENT FROM CREDIT!';
   THROW 50000, @Mesage ,1
END

**問題:**在進行一些驗證後,在 SP 內有一個游標為每次迭代插入詳細記錄。即使我們沒有在循環內使用事務控制,是否可以送出一個插入而回滾另一個插入?

經過大量測試,我找到了答案。是的,它可以,因為您執行它是 AD HOC,沒有事務控制。

create table dbo.TOBEINSERTEDINTO
(
 ID int not null
)
GO

/*********************************************************************/
/***   Test scenario                                               ***/
/*********************************************************************/
create procedure TestCursor
as
begin
   declare @id int

   declare cursor_test cursor read_only fast_forward
   for
   select * from (values(1),(2),(3),(null)) as src(id) order by id desc

   open cursor_test

   declare @status int = 0 

       while @status = 0
       begin
           fetch next from cursor_test into @id

           set @status = @@fetch_status

           if (@status = 0) 
           begin

               if (@id is null)
               begin
                   throw 50000, n'!!!!!!!!! id cannot be NULL !!!!!!!!!!' ,1
               end

               insert into dbo.TOBEINSERTEDINTO(ID) values(@id)
           end
       end

   close cursor_test
   deallocate cursor_test
end;
GO

/*********************************************************************/
/***   Calling inside a transaction, with a try..catch can be ok   ***/
/*********************************************************************/

begin tran
begin try
   Exec TestCursor
end try

begin catch
   if @@trancount > 0  
   rollback
end catch

if @@trancount > 0  
   commit transaction; 

select * from dbo.TOBEINSERTEDINTO

GO

/*********************************************************************/
/***   Lack of transaction control leads to inconsistence          ***/
/*********************************************************************/
exec TestCursor
GO

select * from dbo.TOBEINSERTEDINTO
GO

該應用程序獲得了可靠的事務控制。

問題的產生是因為有人篡改了基地。我猜有人執行了一個配置文件並試圖模仿一些應用程序呼叫。

引用自:https://dba.stackexchange.com/questions/259167