在現實場景中,我們經常會遇到修改數據類型的場景,尤其是自增列從INT修改為BIGINT的情況,自增列又通常作為表的主鍵和聚集索引鍵,因此修改操作需要按以下步驟來進行
1. 停止對該表的訪問(通過禁用權限或停應用的方式實現)
2. 刪除非聚集索引
3. 刪除主鍵聚集索引
4. 使用ALTER TABLE ALTER COLUMN來修改
5. 創建主鍵聚集索引
6. 創建非聚集索引
此方式有以下缺點:
1. 整個ALTER COLUMN操作作為一個事務,需要對將每條數據修改操作記錄到日志中,中途撤銷修改需要長時間回滾。
2. 根據頁面碎片情況,修改類型操作可能造成大量的頁拆分,導致日志文件暴增。
3. 影響正常業務的周期較長。
針對大事務和頁拆分的問題,可以進行以下改進: 新建表,將現有表中數據導入到新表中,數據導入完成后,修改新表名稱為現有表名,當仍無法解決影響周期較長的問題,在導入數據期間,允許程序進行只讀訪問,降低修改類型操作對業務的影響,但數據長時間不可修改,這對很多重要業務也是不可接受的。
為解決此問題,肖桑提出了復制環路的解決辦法,采用多級復制的方式,將舊表數據復制到新表中,然后停止舊表讀寫,等所有修改同步到新表后,再修改表名,以實現數據類型變更的目的,除前期准備時間外,整個操作對業務影響時間可以控制在幾分鍾以內。
采用導入新表的方式,為保證新舊兩表數據一致,必須限制對舊表的修改操作,如果能保證某一時間點的數據被完整地導入到新表,並且保證在導入過程中所有發生在舊表上的操作被“同步”到新表中,那便可以實現新舊兩表在特定時間點數據一致。
實現方式:
1. 使用數據庫快照來將舊表中某一點的數據導入到新表
2. 使用更改跟蹤來將自快照后所有發生在舊表上的數據變更更新到新表上
測試步驟及測試代碼:
1. 對數據庫啟用更改跟蹤
--=================================== --對數據庫TesDB2啟用CT功能 ALTER DATABASE TesDB2 SET CHANGE_TRACKING = ON (CHANGE_RETENTION = 2 DAYS, AUTO_CLEANUP = ON) GO
2. 創建測試表和生成模擬數據
--===================================== --創建測試表 CREATE TABLE TB001 ( ID INT IDENTITY(1,1) PRIMARY KEY, C1 NVARCHAR(200) ) GO --================================== --插入數據 INSERT INTO TB001(C1) SELECT T1.name FROM sys.all_columns T1 GO
3. 對表啟用更改跟蹤
--=================================== --對表TB001啟用CT功能 ALTER TABLE TB001 ENABLE CHANGE_TRACKING WITH (TRACK_COLUMNS_UPDATED = ON) GO
4. 創建新表,並將數據導入到新表中
--================================== --創建測試表 CREATE TABLE TB002 ( ID BIGINT IDENTITY(1,1) PRIMARY KEY, C1 NVARCHAR(200) ) GO --================================== --將TB001的數據導入到TB002中 --生產環境可以使用快照方式來保證數據一致性 SET IDENTITY_INSERT TB002 ON INSERT TB002(ID,C1) SELECT ID,C1 FROM TB001 SET IDENTITY_INSERT TB002 OFF GO
5. 模擬數據庫上變化,然后將禁用賬戶對表的修改權限
--====================================== --模擬TB001上數據變化 INSERT INTO TB001(C1) SELECT TOP(100) T1.name FROM sys.all_columns T1 GO UPDATE TB001 SET C1='UPDATEDATA' WHERE ID%10=1 GO DELETE TB001 WHERE ID%4=1 GO
6. 將在舊表上的刪除操作“同步”到新表上
--====================================== --刪除TB001中不存在但在TB002中存在的數據 WITH T1 AS ( SELECT ROW_NUMBER() OVER (PARTITION BY ID ORDER BY CT.SYS_CHANGE_VERSION DESC) AS RowNum, * FROM CHANGETABLE(CHANGES [dbo].[TB001],0) AS CT ), T2 AS ( SELECT * FROM T1 WHERE RowNum = 1 AND SYS_CHANGE_OPERATION = 'D' ) DELETE FROM [dbo].[TB002] WHERE ID IN ( SELECT ID FROM T2 ) GO
7. 將在舊表上的刪除操作“同步”到新表上
--====================================== --根據TB001中插入和更新的數據來更新TB002 GO SET IDENTITY_INSERT [dbo].[TB002] ON ;WITH T1 AS ( SELECT ROW_NUMBER() OVER (PARTITION BY ID ORDER BY CT.SYS_CHANGE_VERSION DESC) AS RowNum, * FROM CHANGETABLE(CHANGES [dbo].[TB001],0) AS CT ), T2 AS ( SELECT * FROM T1 WHERE RowNum = 1 AND (SYS_CHANGE_OPERATION = 'U' OR SYS_CHANGE_OPERATION = 'I') ), T3 AS ( SELECT * FROM [dbo].[TB001] WHERE ID IN (SELECT ID FROM T2) ), T4 AS ( SELECT * FROM [dbo].[TB002] WHERE ID IN (SELECT ID FROM T2) ) MERGE T4 AS T USING T3 AS S ON T.ID=S.ID WHEN MATCHED THEN UPDATE SET T.C1=S.C1 WHEN NOT MATCHED BY TARGET THEN INSERT( [ID], [C1] ) VALUES ( [ID], [C1] ); SET IDENTITY_INSERT [dbo].[TB002] OFF GO
8. 檢查兩表數據是否一致,生產環境中建議使用SP_SPACEUSED來查看表數據
--==================================================== --檢查數據是否相同,如果下面查詢沒有數據,則證明數據一致 SELECT * FROM ( SELECT * FROM TB001 EXCEPT SELECT * FROM TB002 ) AS T1 UNION SELECT * FROM ( SELECT * FROM TB002 EXCEPT SELECT * FROM TB001 ) AS T2 GO
9. 清理測試環境
--================================== --禁用表級別更改跟蹤 ALTER DATABASE [TestDB2] SET CHANGE_TRACKING = OFF --================================== --禁用表級別更改跟蹤 ALTER TABLE TB001 DISABLE CHANGE_TRACKING; --===================================== --刪除測試表 DROP TABLE TB001 DROP TABLE TB002
由於我們追求的是最終數據一致,因此使用ROW_NUMBER() OVER (PARTITION BY ID ORDER BY CT.SYS_CHANGE_VERSION DESC) AS RowNum =1的方式來過濾中間變更,如果使用SYS_CHANGE_COLMNS會“嚴重”增加代碼復雜性,因此采用更新所有字段的暴力方式實現。
如果導入數據的過程持續時間較長,該期間內數據變化較大,可以考慮先實現一次“同步”后,再禁用舊表的訪問權限,然后再做一次“同步”操作,以降低最后一次“同步”的運行時間。
--============================================================
好久米寫文臟,隨便整個妹子鎮貼。