隨機錯誤 824 - 所有環境
我們在 DEV/QA/PROD SQL 2017 Enterprise 伺服器上遇到了看似隨機的錯誤 824。伺服器執行幾乎相同的程式碼,通過 ETL 流程將相同的日常文件攝取到我們的數據倉庫中。這些錯誤在 2022 年 5 月左右首次發現,但由於日誌清理,我們無法確定(供應商提供的)ETL 程序是否正在擷取這些錯誤、記錄警告並繼續處理而不是失敗!
DEV/QA 已修補到 CU30(最新的 CU)——條件仍然存在。CU22 的生產落後了幾個更新檔,併計劃在未來幾週內進行修補。
例子:
SQL Server 檢測到基於邏輯不一致的 I/O 錯誤:校驗和不正確(預期為 0xc30164e7;實際為 0x9f2bc675c)。它發生在讀取文件“H:\tempdb_mssql_6.ndf”中偏移量 0x0000027de40000 的數據庫 ID 2 中的頁面 (7:1306400) 期間。
如前所述,這在我們所有的環境中都是隨機發生的。所有伺服器都是虛擬化的。DEV/QA 都使用相同的 SAN。生產在不同數據中心的單獨 SAN 上。我沒有關於 SAN 設備的品牌/型號的詳細資訊。
在大多數情況下,當這種情況發生時,它似乎主要在 tempdb 中(但並非總是如此)。此外,suspect_pages 通常是空的。它似乎也更頻繁地發生在星期六,因為我們連續發生了 3-4 次。
還要注意的是,錯誤中列出的預期/實際值通常是相同的——但並非總是如此。
還注意到,特定的儲存過程似乎更容易受到引發此錯誤的影響,但是,它已在 ETL 作業的多個其他位置發生,再次影響不同的數據庫。似乎觸發此錯誤的儲存過程通常添加一個 PERSISTED 計算列,然後基於該計算列添加一個 ROW_NUMBER() 到 5 個表,大小範圍從 200K 到 7.5M 行。我們昨天(在 QA 中)修改了此過程,以限制使用 ROW_NUMBER() 值更新的行數(僅當 rownum=1 時)並將該更新從一次全部更改為 25K 批處理方法。該錯誤今天在 QA 中再次發生——因此我們刪除了計算列上的 PERSISTED 選項。我們實際上正在嘗試任何事情來阻止質量檢查中的這種情況,因為它似乎受到的影響最大。
DBCC CHECKDB 每天在所有生產數據庫上執行,一個除外。一個跳過的數據庫非常大,接近 4TB,DBCC CHECKDB 大約需要 12 小時才能完成。DBCC CHECKDB 已在此大型數據庫的 QA 中最近的生產備份上執行,並且很乾淨。
到目前為止,只有 tempdb 在生產中受到影響。DEV/QA 已經看到其他幾個數據庫受到影響,但 DBCC CHECKDB 沒有產生任何結果,即使懷疑頁麵包含一個條目。當它不是 tempdb 時,我們已將生產數據庫恢復到 DEV/QA,只是為了安全。
當 tempdb 受到影響時,我們一直在停止 SQL,並刪除有問題的 tempdb 文件並重新啟動服務。
值得注意的是,H: 在生產中的使用率為 100%,因為 mdf/ndf 文件佔用了整個 150GB 驅動器,但文件中有足夠的可用空間。DEV/QA 沒有達到 100%,並且有大約 30-40GB 的可用空間,並且都顯示 tempdb 受到影響。所以我認為這不是驅動器空間問題。
我已經聯繫了我們的 DBA,他認為問題是由數據本身引起的。雖然我不同意數據有問題,但我們確實在 2022 年 4 月升級了生成由 ETL 作業讀取的 CSV 文件的系統。源系統現在基於 linux 而不是基於 Windows。ETL 過程無需更改即可讀取新文件。文件中的數據在結構上發生了一些變化,但它們在很大程度上是相同的,CLR procs 可以根據每天發送的模式佈局文件來適應結構變化。這些文件足夠大(每天 55+GB),它們填充了 300 多個表,每個表都包含大量的 nvarchar(max) 欄位。
我已聯繫我們的基礎架構團隊以檢查 VM/SAN 執行狀況——雖然生產的 VM 在文件攝取階段報告了高 IO(時間戳完美對齊)——但沒有報告錯誤。我們的基礎架構團隊在調查期間將 prod 虛擬機遷移到更快的主機,並將文件移動到新的相對未使用的較新的 SAN。
7 月 26 日,DEV/QA SAN 發生驅動器故障,導致兩個驅動器被更換和重建。由於驅動器故障,我們在與 Microsoft 合作時沒有發生任何事故,但是上週六和上週一 - QA 遇到了更多與 tempdb 相關的錯誤。
我們已經向 Microsoft 開了一張票,經過幾週/電話,我們被告知他們是錯誤的團隊(他們只幫助修復腐敗),我們需要一張高級票。因此,我們目前正在向 Microsoft 索取報價,以獲取幫助進行根本原因分析的首要票證。
同時,我希望生產不會有進一步的問題,我希望有人能夠提供一些進一步的見解或指導,我們可以做些什麼來分析這個問題,或者限制它的發生,直到我們能得到微軟正確參與。
編輯 1a - 完整的 @@VERSION 輸出
DEV: Microsoft SQL Server 2017 (RTM-CU30) (KB5013756) - 14.0.3451.2 (X64) Jun 22 2022 18:20:15 Copyright (C) 2017 Microsoft Corporation Enterprise Edition: Core-based Licensing (64-bit) on Windows Server 2012 R2 Datacenter 6.3 <X64> (Build 9600: ) (Hypervisor) QA: Microsoft SQL Server 2017 (RTM-CU30) (KB5013756) - 14.0.3451.2 (X64) Jun 22 2022 18:20:15 Copyright (C) 2017 Microsoft Corporation Enterprise Edition: Core-based Licensing (64-bit) on Windows Server 2012 R2 Datacenter 6.3 <X64> (Build 9600: ) (Hypervisor) PROD: Microsoft SQL Server 2017 (RTM-CU22-GDR) (KB4583457) - 14.0.3370.1 (X64) Nov 6 2020 18:19:52 Copyright (C) 2017 Microsoft Corporation Enterprise Edition: Core-based Licensing (64-bit) on Windows Server 2012 R2 Datacenter 6.3 <X64> (Build 9600: ) (Hypervisor)
編輯 1b - 可疑程序 失敗發生在以下註釋的程式碼中:BATCH UPDATE RN - CURRENT/PAST ROWS
SET NOCOUNT ON; DECLARE @schema SYSNAME, @table SYSNAME, @RNCurrDepth SMALLINT, @RNFutureDepth SMALLINT, @SQL NVARCHAR(MAX); BEGIN TRY DECLARE IDComp3 CURSOR LOCAL FOR SELECT DISTINCT S.name, T.name, P.RNCurrentDescDepth, P.RNFutureAscDepth FROM sys.columns C JOIN sys.tables T ON C.object_id=T.object_id JOIN sys.schemas S ON T.schema_id=S.schema_id JOIN dbo.ParseIDComp3Control P ON S.name=P.TableSchema AND T.name=TableName /* Control Table to limit the tables parsed, and how many ROW_NUMBERS to update */ WHERE C.name='ID_COMP_3' AND P.Active=1 ORDER BY T.name; OPEN IDComp3; FETCH NEXT FROM IDComp3 INTO @schema, @table, @RNCurrDepth, @RNFutureDepth; WHILE @@FETCH_STATUS=0 BEGIN --RAISERROR(@TABLE,0,1) WITH NOWAIT; DROP TABLE IF EXISTS #TMP; CREATE TABLE #TMP ([@ID] NVARCHAR(150) ,LEAD_CO_MNE NVARCHAR(50) ,RNCURRENTDESC INT ,RNFUTUREASC INT); DROP TABLE IF EXISTS #TMP_BATCH; CREATE TABLE #TMP_BATCH ([@ID] NVARCHAR(150) ,LEAD_CO_MNE NVARCHAR(50) ,RNCURRENTDESC INT ,RNFUTUREASC INT); /* DROP ID_COMP_3 COLUMNS IF EXISTS, TO ENSURE WE HAVE NO VALUES */ SET @SQL=''; SELECT @SQL+='ALTER TABLE '+QUOTENAME(S.name)+'.'+QUOTENAME(T.name)+' DROP COLUMN '+QUOTENAME(C.name)+';'+CHAR(10) FROM sys.columns C JOIN sys.tables T ON C.object_id=T.object_id JOIN sys.schemas S ON T.schema_id=S.schema_id WHERE C.name IN ('ID_COMP_3_DATE','ID_COMP_3_ID') AND S.name=@schema AND T.name=@table EXEC SP_EXECUTESQL @SQL; /* ADD COMPUTED COLUMNS IF NOT EXISTS */ IF NOT EXISTS (SELECT 1 FROM sys.columns C JOIN sys.tables T ON C.object_id=T.object_id JOIN sys.schemas S ON T.schema_id=S.schema_id WHERE C.name='ID_COMP_3_DATE' AND S.name=@schema AND T.name=@table) BEGIN SET @SQL = 'ALTER TABLE '+QUOTENAME(@schema)+'.'+QUOTENAME(@table)+'ADD ID_COMP_3_DATE AS TRY_CONVERT(DATE,LEFT(ID_COMP_3,8),112), ID_COMP_3_ID AS TRY_CONVERT(INT,RIGHT(ID_COMP_3,LEN(ID_COMP_3)-CHARINDEX(''.'',ID_COMP_3)));' EXEC SP_EXECUTESQL @SQL; END /* DROP INDEXES IF EXISTS (SO WE CAN DROP COLUMNS) */ SET @SQL=''; SELECT @SQL+='DROP INDEX ' + QUOTENAME(I.name) + ' ON ' + QUOTENAME(S.name)+'.'+QUOTENAME(T.name)+CHAR(10) FROM sys.indexes I JOIN sys.tables T ON I.object_id=T.object_id JOIN sys.schemas S ON T.schema_id=S.schema_id WHERE T.name=@table AND S.name=@schema AND I.name IN ('IX_'+t.name+'_RN_Current_Desc', 'IX_'+t.name+'_RN_Future_Asc') EXEC SP_EXECUTESQL @SQL; /* DROP RN COLUMNS IF EXISTS, TO ENSURE WE HAVE NO VALUES */ SET @SQL=''; SELECT @SQL+='ALTER TABLE '+QUOTENAME(S.name)+'.'+QUOTENAME(T.name)+' DROP COLUMN '+QUOTENAME(C.name)+';'+CHAR(10) FROM sys.columns C JOIN sys.tables T ON C.object_id=T.object_id JOIN sys.schemas S ON T.schema_id=S.schema_id WHERE C.name IN ('RN_Current_Desc','RN_Future_Asc') AND S.name=@schema AND T.name=@table EXEC SP_EXECUTESQL @SQL; /* ADD RN COLUMNS */ IF NOT EXISTS (SELECT 1 FROM sys.columns C JOIN sys.tables T ON C.object_id=T.object_id JOIN sys.schemas S ON T.schema_id=S.schema_id WHERE C.name='RN_Current_Desc' AND S.name=@schema AND T.name=@table) BEGIN SET @SQL = 'ALTER TABLE '+QUOTENAME(@schema)+'.'+QUOTENAME(@table)+'ADD RN_Current_Desc SMALLINT, RN_Future_Asc SMALLINT;' EXEC SP_EXECUTESQL @SQL; END /* ADD INDEX TO @ID/RNS */ SET @SQL = 'CREATE NONCLUSTERED INDEX [IX_'+@table+'_RN_Current_Desc] ON '+QUOTENAME(@schema)+'.'+QUOTENAME(@table)+' ([@ID], LEAD_CO_MNE, RN_Current_Desc ASC) WHERE (RN_Current_Desc <= '+CONVERT(VARCHAR(6),@RNCurrDepth)+'); CREATE NONCLUSTERED INDEX [IX_'+@table+'_RN_Future_Asc] ON '+QUOTENAME(@schema)+'.'+QUOTENAME(@table)+' ([@ID], LEAD_CO_MNE, RN_Future_Asc DESC) WHERE (RN_Current_Desc <= '+CONVERT(VARCHAR(6),@RNFutureDepth)+'); CREATE NONCLUSTERED INDEX [IX_TMP_Current_Desc] ON #TMP ([@ID], LEAD_CO_MNE) INCLUDE(RNCURRENTDESC, RNFUTUREASC); CREATE NONCLUSTERED INDEX [IX_TMPBATCH_Current_Desc] ON #TMP_BATCH ([@ID], LEAD_CO_MNE) INCLUDE(RNCURRENTDESC, RNFUTUREASC);'; EXEC SP_EXECUTESQL @SQL; /* BATCH UPDATE RN - CURRENT/PAST ROWS */ SET @SQL = ';WITH X AS ( SELECT [@ID] ,LEAD_CO_MNE ,ROW_NUMBER() OVER(PARTITION BY ID_COMP_1, LEAD_CO_MNE ORDER BY ID_COMP_3_DATE DESC, ID_COMP_3_ID DESC) AS RNCURRENTDESC FROM '+QUOTENAME(@schema)+'.'+QUOTENAME(@table)+' WHERE ID_COMP_3_DATE <= MIS_DATE ) INSERT INTO #TMP ([@ID],LEAD_CO_MNE,RNCURRENTDESC) SELECT [@ID], LEAD_CO_MNE, RNCURRENTDESC FROM X WHERE RNCURRENTDESC <= @RNCurrDepth; DECLARE @BATCH INT = 25000; WHILE @BATCH > 0 BEGIN UPDATE TOP(@BATCH) X SET RN_Current_Desc = T.RNCURRENTDESC FROM '+QUOTENAME(@schema)+'.'+QUOTENAME(@table)+' X JOIN #TMP T ON X.[@ID]=T.[@ID] AND X.LEAD_CO_MNE=T.LEAD_CO_MNE WHERE X.RN_Current_Desc IS NULL; SET @BATCH=@@ROWCOUNT; END' EXEC SP_EXECUTESQL @SQL, N'@RNCurrDepth SMALLINT', @RNCurrDepth=@RNCurrDepth; TRUNCATE TABLE #TMP; /* RN - FUTURE ROWS */ SET @SQL = ';WITH X AS ( SELECT [@ID] ,LEAD_CO_MNE ,ROW_NUMBER() OVER(PARTITION BY ID_COMP_1, LEAD_CO_MNE ORDER BY ID_COMP_3_DATE ASC, ID_COMP_3_ID DESC) AS RNFUTUREASC FROM '+QUOTENAME(@schema)+'.'+QUOTENAME(@table)+' WHERE ID_COMP_3_DATE > MIS_DATE ) INSERT INTO #TMP ([@ID],LEAD_CO_MNE,RNFUTUREASC) SELECT [@ID],LEAD_CO_MNE,RNFUTUREASC FROM X WHERE RNFUTUREASC <= @RNFutureDepth DECLARE @BATCH INT = 25000; WHILE @BATCH > 0 BEGIN UPDATE TOP(@BATCH) X SET RN_Future_Asc = T.RNFUTUREASC FROM '+QUOTENAME(@schema)+'.'+QUOTENAME(@table)+' X JOIN #TMP T ON X.[@ID]=T.[@ID] AND X.LEAD_CO_MNE=T.LEAD_CO_MNE WHERE X.RN_Future_Asc IS NULL; SET @BATCH=@@ROWCOUNT; END' EXEC SP_EXECUTESQL @SQL, N'@RNFutureDepth SMALLINT', @RNFutureDepth=@RNFutureDepth; FETCH NEXT FROM IDComp3 INTO @schema, @table, @RNCurrDepth, @RNFutureDepth; END CLOSE IDComp3 DEALLOCATE IDComp3 END TRY BEGIN CATCH DECLARE @Message NVARCHAR(MAX) = ERROR_MESSAGE(), @Severity INT = ERROR_SEVERITY(), @State SMALLINT =ERROR_STATE() RAISERROR(@Message, @Severity, @State); RETURN(1); END CATCH RETURN(0)
編輯 1c - 範例表架構
CREATE TABLE dbo.ABC123 ( [LEAD_CO_MNE] [nvarchar](50) NOT NULL, [BRANCH_CO_MNE] [nvarchar](50) NULL, [MIS_DATE] [date] NOT NULL, [@ID] [nvarchar](150) NOT NULL, [ACTIVITY] [nvarchar](150) NULL, [ACTION] [nvarchar](max) NULL, [CHANGE_DATE_TYPE] [nvarchar](50) NULL, [CHANGE_PERIOD] [nvarchar](150) NULL, [CHANGE_DATE] [datetime2](7) NULL, [CHANGE_ACTIVITY] [nvarchar](150) NULL, [PRIOR_DAYS] [int] NULL, [CHG_TO_PRODUCT] [nvarchar](150) NULL, [ALLOWED_PRODUCT] [nvarchar](max) NULL, [RESERVED_6] [nvarchar](150) NULL, [RESERVED_5] [nvarchar](150) NULL, [INITIATION_TYPE] [nvarchar](50) NULL, [DEFAULT_ACTIVITY] [nvarchar](150) NULL, [RESERVED_4] [nvarchar](150) NULL, [RESERVED_3] [nvarchar](150) NULL, [RESERVED_2] [nvarchar](150) NULL, [RESERVED_1] [nvarchar](150) NULL, [LOCAL_REF] [nvarchar](max) NULL, [PR_ATTRIBUTE] [nvarchar](max) NULL, [PR_VALUE] [nvarchar](max) NULL, [PR_BRK_RES] [nvarchar](max) NULL, [PR_BRK_MSG] [nvarchar](max) NULL, [PR_BRK_CHARGE] [nvarchar](max) NULL, [PR_RESERVED_3] [nvarchar](max) NULL, [PR_RESERVED_2] [nvarchar](max) NULL, [PR_RESERVED_1] [nvarchar](max) NULL, [PR_APP_METHOD] [nvarchar](max) NULL, [PR_APP_PERIOD] [nvarchar](max) NULL, [SYS_RESERVE7] [nvarchar](150) NULL, [SYS_RESERVE6] [nvarchar](150) NULL, [SYS_RESERVE5] [nvarchar](150) NULL, [SYS_RESERVE4] [nvarchar](150) NULL, [SYS_RESERVE3] [nvarchar](150) NULL, [SYS_RESERVE2] [nvarchar](150) NULL, [SYS_RESERVE1] [nvarchar](150) NULL, [DEFAULT_ATTR_OPTION] [nvarchar](150) NULL, [DEFAULT_NEGOTIABLE] [nvarchar](50) NULL, [NR_ATTRIBUTE] [nvarchar](max) NULL, [NR_OPTIONS] [nvarchar](max) NULL, [NR_RESERVED2] [nvarchar](max) NULL, [NR_RESERVED1] [nvarchar](max) NULL, [NR_STD_COMP] [nvarchar](max) NULL, [NR_TYPE] [nvarchar](max) NULL, [NR_VALUE] [nvarchar](max) NULL, [NR_MESSAGE] [nvarchar](max) NULL, [CHANGED_FIELDS] [nvarchar](max) NULL, [NEGOTIATED_FLDS] [nvarchar](max) NULL, [ID_COMP_1] [nvarchar](150) NULL, [ID_COMP_2] [nvarchar](150) NULL, [ID_COMP_3] [nvarchar](150) NULL, [ID_COMP_4] [nvarchar](150) NULL, [ID_COMP_5] [nvarchar](150) NULL, [ID_COMP_6] [nvarchar](150) NULL, [RESERVED2_ID] [nvarchar](150) NULL, [TARGET_PRODUCT] [nvarchar](50) NULL, [STMT_NOS] [nvarchar](max) NULL, [OVERRIDE] [nvarchar](max) NULL, [RECORD_STATUS] [nvarchar](50) NULL, [CURR_NO] [int] NULL, [INPUTTER] [nvarchar](max) NULL, [DATE_TIME] [nvarchar](max) NULL, [AUTHORISER] [nvarchar](150) NULL, [CO_CODE] [nvarchar](50) NULL, [DEPT_CODE] [nvarchar](50) NULL, [AUDITOR_CODE] [nvarchar](50) NULL, [AUDIT_DATE_TIME] [int] NULL, [ARRANGEMENT_KEY] [nvarchar](150) NULL, [ETL_DQ_RevisionCount] [int] NULL, [ETL_DQ_ColumnsRevised] [nvarchar](4000) NULL, [ETL_DQ_ErrorMessage] [nvarchar](4000) NULL, [ETL_CHANGE_PERIOD] [nvarchar](100) NULL, [API_ATTRIBUTE] [nvarchar](max) NULL, [NR_ATTRIBUTE_RULE] [nvarchar](max) NULL, [NR_VALUE_SOURCE] [nvarchar](max) NULL, [OWNING_COMPANY] [nvarchar](max) NULL, [ID_COMP_3_DATE] AS (TRY_CONVERT([date],left([ID_COMP_3],(8)),(112))), [ID_COMP_3_ID] AS (TRY_CAST(right([ID_COMP_3],len([ID_COMP_3])-charindex('.',[ID_COMP_3])) AS [int])), [RN_Current_Desc] [smallint] NULL, [RN_Future_Asc] [smallint] NULL, CONSTRAINT [PK_ABC123] PRIMARY KEY NONCLUSTERED ( [@ID] ASC, [LEAD_CO_MNE] ASC ) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] GO
82x 錯誤很少(如果有的話)是 SQL Server 問題,因為這些都是在返回 SQL Server 程式碼時沒有意義或出錯的錯誤程式碼。例如,對於 824,這可能是由錯誤的驅動程序或 SQL 下的記憶體層(包括硬體和軟體,如虛擬機管理程序、SAN、磁碟控制器等)引起的。
有幾件事可以嘗試縮小問題範圍:
- 執行SQLIOSIM以查看是否發生任何錯誤。如果他們這樣做了,那就不是 SQL Server。
- 檢查您的管理程序的已知問題 - 例如導致 823/824 錯誤並進行適當配置更改的VMWare 驅動程序。
- 查看並查看 storport 跟踪,以查看是否有其他問題可能有助於在該時間段內導致問題以及返回的數據。
你看過https://www.sqlservercentral.com/blogs/vmware-sql-server-823-824-alerts嗎?不確定您是否在 VMWare 上執行以及哪個版本,但 VMware vSphere 7.0 Update 2 存在一個已知問題,這聽起來與您正在經歷的完全一樣。