SQL 事務日誌是否在循環結束時清除?
我正在執行 SQL Server 2019 企業版。每個月,我們都會執行一個儲存過程來載入數百萬條服務日期可以追溯到五年前的記錄。我為 4 個數據文件分配了 400GB 的空間,並為日誌文件分配了 100GB 的空間。作業經常失敗,因為日誌文件因活動事務而被填滿。數據庫處於簡單恢復模式。所以,我相信它應該在每筆交易結束時清除。開發人員更改了工作,使其一次循環並載入一年的記錄。
DROP TABLE IF EXISTS #UnpvtDx; SELECT ClaimHeader_ID ,ClaimDetail_ID ,ClaimServiceLine ,Unpvt.CodeLine ,Unpvt.DxCode INTO #UnpvtDx FROM PRINCE.Claim.ClaimDetail det WITH (NOLOCK) UNPIVOT ( DxCode FOR CodeLine IN ( Diagnosis1CD,Diagnosis2CD,Diagnosis3CD,Diagnosis4CD,Diagnosis5CD, Diagnosis6CD,Diagnosis7CD,Diagnosis8CD,Diagnosis9CD, Diagnosis10CD,Diagnosis11CD,Diagnosis12CD,Diagnosis13CD ) ) as Unpvt ---53 secs WHERE YEAR(ServiceFromDT) = @year;
DROP TABLE IF EXISTS #UnpvtPointer; SELECT ClaimHeader_ID ,ClaimDetail_ID ,ClaimServiceLine ,Unpvt.CodeLine ,Unpvt.Pointer INTO #UnpvtPointer FROM PRINCE.Claim.ClaimDetail det WITH (NOLOCK) UNPIVOT ( Pointer FOR CodeLine IN (DiagPointer1,DiagPointer2,DiagPointer3,DiagPointer4) ) as Unpvt ---40 secs WHERE YEAR(ServiceFromDT) = @year;
INSERT INTO PROD.Claim.ClaimDiag ( ClaimHeader_ID,ClaimDetail_ID,SourceID,EDWLoadDTS,PartnerCD, PartnerNM,ClaimID,ClaimServiceLine,ClaimStatus,CCOMemberID, MemberID,PlaceOfServiceCD,ServiceFromDT,ServiceToDT,ClaimForm, TypeOfBillCD,DiagnosisCD,DiagnosisDESC,DiagPointer ) SELECT DISTINCT det.ClaimHeader_ID,det.ClaimDetail_ID,det.SourceID,det.EDWLoadDTS, det.PartnerCD,det.PartnerNM,det.ClaimID,det.ClaimServiceLine, det.ClaimStatus,det.CCOMemberID,det.MemberID,det.PlaceOfServiceCD, det.ServiceFromDT,det.ServiceToDT,det.ClaimForm,det.TypeOfBillCD, DiagnosisCD = dx.DxCode, DiagnosisDESC = diag.DiagnosisDESC, DiagPointer = point.Pointer FROM PROD.Claim.ClaimDetail det WITH (NOLOCK) INNER JOIN PROD.Claim.ClaimHeader ch WITH (NOLOCK) ON ch.ClaimHeader_ID = det.ClaimHeader_ID INNER JOIN #UnpvtDx dx ON dx.ClaimDetail_ID = det.ClaimDetail_ID AND dx.ClaimHeader_ID = det.ClaimHeader_ID AND dx.ClaimServiceLine = det.ClaimServiceLine LEFT JOIN #UnpvtPointer point ON point.ClaimDetail_ID = det.ClaimDetail_ID AND point.ClaimHeader_ID = det.ClaimHeader_ID AND point.ClaimServiceLine = det.ClaimServiceLine LEFT OUTER JOIN Reference.Reference.Diagnosis diag WITH (NOLOCK) ON dx.DxCode = diag.DiagnosisCD AND diag.ICDVersion = 'ICD10CM' AND diag.ActiveFLG = 1 WHERE YEAR(det.ServiceFromDT) = @year;
使用以下命令從 SQL 代理作業執行儲存過程:
DECLARE @year INT DECLARE cur CURSOR FOR SELECT yr = YEAR(hdr.MinServiceFromDT) FROM PROD.Claim.ClaimHeader hdr WITH (NOLOCK) GROUP BY YEAR(hdr.MinServiceFromDT) ORDER BY YEAR(hdr.MinServiceFromDT) OPEN cur FETCH NEXT FROM cur INTO @year WHILE @@FETCH_STATUS = 0 BEGIN EXEC Claim.sp_UpdateClaimDiag @year FETCH NEXT FROM cur INTO @year END CLOSE cur DEALLOCATE cur
循環的結束是否被認為是一個事務,因此在處理了每一年的記錄後日誌文件應該清空,或者日誌文件是否繼續填充,直到作業遍歷每個循環並載入所有年份的記錄?
我還將日誌文件增加到 150GB,但這會最大化可用空間(不會低於 10% 的緩衝區)。
發布整個儲存過程程式碼。
USE [Prod] GO SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO CREATE PROCEDURE [Claim].[sp_UpdateClaimDiag] @year INT AS BEGIN SET ANSI_DEFAULTS, ARITHABORT, NOCOUNT ON SET IMPLICIT_TRANSACTIONS OFF SET TRANSACTION ISOLATION LEVEL READ COMMITTED -- variable declaration DECLARE @transactional BIT , @trancount INT , @err INT , @procname SYSNAME , @error INT , @message VARCHAR(4000) , @xstate INT , @RecordCount int; SELECT @procname = OBJECT_SCHEMA_NAME(@@PROCID, DB_ID()) + '.' + OBJECT_NAME(@@PROCID, DB_ID()) -- 0 = no: will not execute batches inside a transaction; a partial success of procedure is possible -- 1 = yes: batches in procedure will be bound together by a transaction, partial success is impossible , @transactional = 0 -- optionally begin transaction and begin try block IF @transactional = 1 SET @trancount = @@TRANCOUNT BEGIN TRY IF @trancount = 0 and @transactional = 1 BEGIN TRANSACTION ELSE IF @transactional = 1 SAVE TRANSACTION p1 ---------------------------------------------------------------------------------------- ---Unpivot columns to Rows into Temp tables ---------------------------------------------------------------------------------------- ---ICD9CM & ICD10CM DROP TABLE IF EXISTS #UnpvtDx; SELECT ClaimHeader_ID ,ClaimDetail_ID ,ClaimServiceLine ,Unpvt.CodeLine ,Unpvt.DxCode INTO #UnpvtDx FROM Prod.Claim.ClaimDetail det WITH (NOLOCK) UNPIVOT ( DxCode FOR CodeLine IN ( Diagnosis1CD,Diagnosis2CD,Diagnosis3CD, Diagnosis4CD,Diagnosis5CD,Diagnosis6CD, Diagnosis7CD,Diagnosis8CD,Diagnosis9CD, Diagnosis10CD,Diagnosis11CD,Diagnosis12CD, Diagnosis13CD ) ) as Unpvt ---53 secs WHERE YEAR(ServiceFromDT) = @year; --Select top 100 * from #UnpvtDx where DxCode is null DROP TABLE IF EXISTS #UnpvtPointer; SELECT ClaimHeader_ID ,ClaimDetail_ID ,ClaimServiceLine ,Unpvt.CodeLine ,Unpvt.Pointer INTO #UnpvtPointer FROM Prod.Claim.ClaimDetail det WITH (NOLOCK) UNPIVOT ( Pointer FOR CodeLine IN ( DiagPointer1, DiagPointer2, DiagPointer3,DiagPointer4 ) ) as Unpvt ---40 secs WHERE YEAR(ServiceFromDT) = @year; --Select top 100 * from #UnpvtPointer ---------------------------------------------------------------------------------------- --- INSERT INTO yearly records from the temp table ---------------------------------------------------------------------------------------- INSERT INTO Prod.Claim.ClaimDiag ( ClaimHeader_ID, ClaimDetail_ID, SourceID, EDWLoadDTS, PartnerCD, PartnerNM, ClaimID, ClaimServiceLine, ClaimStatus, CCOMemberID, MemberID, PlaceOfServiceCD, ServceFromDT, ServiceToDT, ClaimForm, TypeOfBillCD, DiagnosisCD, DiagnosisDESC, DiagPointer ) SELECT DISTINCT det.ClaimHeader_ID, det.ClaimDetail_ID, det.SourceID, det.EDWLoadDTS, det.PartnerCD, det.PartnerNM, det.ClaimID, det.ClaimServiceLine, det.ClaimStatus, det.CCOMemberID, det.MemberID, det.PlaceOfServiceCD, det.ServiceFromDT, det.ServiceToDT, det.ClaimForm, det.TypeOfBillCD, DiagnosisCD = dx.DxCode, DiagnosisDESC = diag.DiagnosisDESC, DiagPointer = point.Pointer FROM Prod.Claim.ClaimDetail det WITH (NOLOCK) INNER JOIN Prod.Claim.ClaimHeader ch WITH (NOLOCK) ON ch.ClaimHeader_ID = det.ClaimHeader_ID INNER JOIN #UnpvtDx dx ON dx.ClaimDetail_ID = det.ClaimDetail_ID AND dx.ClaimHeader_ID = det.ClaimHeader_ID AND dx.ClaimServiceLine = det.ClaimServiceLine LEFT JOIN #UnpvtPointer point ON point.ClaimDetail_ID = det.ClaimDetail_ID AND point.ClaimHeader_ID = det.ClaimHeader_ID AND point.ClaimServiceLine = det.ClaimServiceLine LEFT OUTER JOIN Reference.Reference.Diagnosis diag WITH (NOLOCK) ON dx.DxCode = diag.DiagnosisCD AND diag.ICDVersion = 'ICD10CM' AND diag.ActiveFLG = 1 WHERE YEAR(det.ServiceFromDT) = @year --AND Year(det.ServiceFromDT) = 2021--for testing --and det.ClaimID ='21006E06455'--for testing ---------------------------------------------------------------------------------------- --insert into updatelog table SET @RecordCount = @@ROWCOUNT; DECLARE @procName1 SYSNAME SET @procName1 = @procname + ' ' + CAST(@year AS varchar(4)) INSERT INTO Prod.dbo.UpdateLog(EventTimestamp,EventDescription,ProcName,TableName) SELECT GETDATE(), 'Inserted ' + CAST(@RecordCount AS varchar(100)) + ' records', @procName1, 'Claim.ClaimDiag' ---------------------------------------------------------------------------------------- DROP TABLE IF EXISTS #UnpvtDx DROP TABLE IF EXISTS #UnpvtPointer ---------------------------------------------------------------------------------------- SPEXIT: IF @transactional = 1 and @trancount = 0 COMMIT END TRY ---------------------------------------------------------------------------------------- -- error handling with catch BEGIN CATCH SELECT @error = ERROR_NUMBER(), @message = ERROR_MESSAGE(), @xstate = XACT_STATE() IF @transactional = 1 and @xstate = -1 ROLLBACK IF @transactional = 1 and @xstate = 1 and @trancount = 0 ROLLBACK IF @transactional = 1 and @xstate = 1 and @trancount > 0 ROLLBACK TRANSACTION p1 DROP TABLE IF EXISTS #claims SET @procName1 = @procname + ' ' + CAST(@year AS varchar(4)) ---+ ', ' + CAST(@month AS varchar(4)) RAISERROR ('%s, Error %d, %s', 16, 1, @procname1, @error, @message) RETURN @error END CATCH RETURN 0 END GO
使用
SIMPLE RECOVERY
模型時,事務日誌在每個檢查點被截斷。不一定在您的交易結束時。檢查點每 60 秒發生一次。查看程式碼的以下部分,您實際上根本沒有明確地開始事務。你最初
set @transactional = 0
,然後才開始一個事務如果@transactional = 1
。此外,您只是@trancount
根據IF
語句在此處設置一個值。該塊仍然開始,因為您在語句之後BEGIN TRY
沒有BEGIN/END
塊。IF
不過,這不是您的問題,它只是意味著您每次都在進行隱式事務,而不是創建顯式事務。SELECT @procname = OBJECT_SCHEMA_NAME(@@PROCID, DB_ID()) + '.' + OBJECT_NAME(@@PROCID, DB_ID()) -- 0 = no: will not execute batches inside a transaction; a partial success of procedure is possible -- 1 = yes: batches in procedure will be bound together by a transaction, partial success is impossible , @transactional = 0 -- optionally begin transaction and begin try block IF @transactional = 1 SET @trancount = @@TRANCOUNT BEGIN TRY IF @trancount = 0 and @transactional = 1 BEGIN TRANSACTION ELSE IF @transactional = 1 SAVE TRANSACTION p1
我懷疑即使一次處理數年的數據,您生成的事務日誌吞吐量也會超出驅動器的支持能力。這張表中一年的數據佔用了多少空間?
考慮將批量大小從一年減少到一個月,看看是否有幫助。當您使用它時,請停止使用 YEAR() 函式圍繞您的日期列。這使得這些列是非 SARGABLE,這意味著 SQL Server 在檢索數據時不能使用任何現有索引。這可能會導致您的事務執行時間超過所需時間,從而導致其他正在進行的事務在此事務執行時保留在日誌中。
考慮以下對儲存過程的更改。我將其簡化了一點,以突出要更改的重要部分。您需要將其與您擁有的內容合併並進行測試。這使您在呼叫過程時更容易控制批量大小。
另外,我刪除了
NOLOCK
提示。在此處閱讀更多內容以了解為什麼這是一個壞主意。NOLCOK
由於非 SARGABLE where 子句,您可能認為您需要提示。CREATE PROCEDURE [Claim].[sp_UpdateClaimDiag] @Interval int = 30 AS BEGIN DECLARE @MinServiceFromDT DATETIME @MaxServiceFromDT DATETIME, @NextServiceFromDT DATETIME; SELECT @MinServiceFromDT = MIN(hdr.MinServiceFromDT), @MaxServiceFromDT = MAX(hdr.MinServiceFromDT) FROM PROD.Claim.ClaimHeader; WHILE @MinServiceFromDT <= @MaxServiceFromDT BEGIN BEGIN TRY @NextServiceFromDT = DATEADD(day,@Interval,@MinServiceFromDT); ---ICD9CM & ICD10CM DROP TABLE IF EXISTS #UnpvtDx; DROP TABLE IF EXISTS #UnpvtPointer; -------------------------------------------------------------- ---Unpivot columns to Rows into Temp tables -------------------------------------------------------------- BEGIN TRANSACTION SELECT ClaimHeader_ID, ... INTO #UnpvtDx FROM Prod.Claim.ClaimDetail det UNPIVOT ( DxCode FOR CodeLine IN ( Diagnosis1CD,... ) ) as Unpvt WHERE ServiceFromDT >= @MinServiceFromDT AND ServiceFromDT < @NextServiceFromDT; SELECT ClaimHeader_ID, ... INTO #UnpvtPointer FROM Prod.Claim.ClaimDetail det UNPIVOT ( Pointer FOR CodeLine IN ( DiagPointer1, DiagPointer2, DiagPointer3,DiagPointer4 ) ) as Unpvt WHERE ServiceFromDT >= @MinServiceFromDT AND ServiceFromDT < @NextServiceFromDT; -------------------------------------------------------------- --- INSERT INTO yearly records from the temp table -------------------------------------------------------------- INSERT INTO Prod.Claim.ClaimDiag (ClaimHeader_ID, ....) SELECT DISTINCT det.ClaimHeader_ID, .... FROM Prod.Claim.ClaimDetail det WITH (NOLOCK) INNER JOIN Prod.Claim.ClaimHeader ch WITH (NOLOCK) ON ch.ClaimHeader_ID = det.ClaimHeader_ID INNER JOIN #UnpvtDx dx ON dx.ClaimDetail_ID = det.ClaimDetail_ID AND dx.ClaimHeader_ID = det.ClaimHeader_ID AND dx.ClaimServiceLine = det.ClaimServiceLine LEFT JOIN #UnpvtPointer point ON point.ClaimDetail_ID = det.ClaimDetail_ID AND point.ClaimHeader_ID = det.ClaimHeader_ID AND point.ClaimServiceLine = det.ClaimServiceLine LEFT OUTER JOIN Reference.Reference.Diagnosis diag ON dx.DxCode = diag.DiagnosisCD AND diag.ICDVersion = 'ICD10CM' AND diag.ActiveFLG = 1 WHERE det.ServiceFromDT >= @MinServiceFromDT AND det.ServiceFromDT < @NextServiceFromDT; -------------------------------------------------------------- --insert into updatelog table COMMIT TRANSACTION SET @ MinServiceFromDT = @NextServiceFromDT END TRY ------------------------------------------------------------------ -- error handling with catch BEGIN CATCH <do error handling stuff> END CATCH END END GO