SQL Server ->> 談SQL Server數據庫大表遷移


前端時間我們生產環境SQL Server數據庫有張表由於當初建表的時候把自增列的數據類型定義成int,估計當初也沒有想到這張表的數據有一天會增長到超過4個子節大小的int32,由於是主鍵又是自增列,所以改數據庫類型是肯定不現實,也不可能停數據庫。所以唯一的辦法就是遷移表數據到另外一張表,然后用新的表替換舊表。這是整個遷移的背景。

做這個事情其實並不復雜,通俗的講就是把A表的數據導到B表,然后把A表表名改掉,把B表表名改成原來A表的表名。事情的達成目標就這么簡單,也可以有N種方法去實現。取決於不同的因素、環境要求,選擇不同的方法。這里想聊聊什么情況下:1、選擇什么樣的方式來做這個事情、2、有哪些實現步驟和檢查步驟是必須要做的。

 

選擇什么樣的方式來做這個事情?

首先我覺得要做這個事情先要了解下面5個方面的情況,按照優先級P1\P2\P3... 去考慮,再決定用什么方法比較合適。

 

表數據遷移的5個考慮點:
    P1:允許表離線的時長

  每個數據庫中的表都承擔着應用程序端或者系統的數據存儲任務,不同系統允許不同表離線的時長是不一樣的,我喜歡把這個時間叫時間窗口(time window)。所以第一步是要先搞清楚表的作用?數據更新頻率?。舉個例子,存配置數據或者基礎信息的一般數據不會受事務交易的影響,一般都是手動更新或者定時更新之類。日志類型的表一般是屬於事務性質的表,但是一般只插入不更新。訂單交易類型表則是會不間斷更新、插入和刪除。第二步是要清楚應用程序是做什么,比如ERP系統,有些ERP是生產系統,只要生產車間是7x24不停歇那就意味着表也是不間斷更新。有些系統是電商平台,也是7x24。了解系統的用途,跟用戶溝通爭取最大的時間窗口。遷移數據雖然技術上可以做到盡可能秒級的無感切換,但是時間窗口是為自己爭取部署后的驗證數據以及可能發生的不確定性問題留有時間余地。所以遷移除了技術方案選擇,我覺得了解系統和用戶溝通同樣非常重要。所以我把這個列為首要考慮因素。


    P2:表數據量大小

  表的數據量大小(包括索引的大小)直接決定了:1、表數據遷移的方案選擇;2、表遷移的執行步驟。 基本上可以分為不同量級,十萬級及以下\百萬計\千萬級\億級及以上。索引數量也是很大的影響因素,索引越多數據寫入越慢,因為數據更新的時候同時也要維護索引,所帶來的影響也是IO的影響,而IO通常來說是數據庫面臨的最大挑戰。基本上通過P1和P2基本就能確定技術方案了(先不考慮后面幾點的影響)。通常我覺得十萬級及以下\百萬級\千萬級,只要不是核心的事務交易型的表,對在線要求不高,有一定時間窗口的(例如1小時以上這種),我覺得都可以優先選擇簡單一點的遷移方案。舉個例子,比如某個字段類型從INT改成BIGINT,如果條件允許(不是主鍵,不是自增列,不是外鍵),表在十萬級及以下\百萬級,可以選擇直接DROP INDEX(假設字段上面有索引) + ALTER TABLE ALTER COLUMN + CREATE INDEX(假設字段上面有索引)改掉,通常來說一張千萬級的表,只要服務器性能不要太差,且表也不是事務交易型的表,從我自身經歷過的例子,都是30分鍾內可以完成類型修改。千萬級我不建議直接改數據類型,主要是如果遇到問題,回滾的時間成本太大,再者千萬級對內存和IO壓力也有影響。千萬級\億級及以上基本是建議用新表替換舊表的形式來做。新表替換舊表也分兩種,

第一種簡單的就是直接 SET IDENTITY_INSERT <新表> ON; INSERT INTO ... SELECT FROM <舊表>; SET IDENTITY_INSERT <新表> OFF;   CREATE INDEX ON <新表>。這種建立在千萬級的表,數據量大概也就是5000萬以下,表寬度小(10個字段以內,不要太多文本字段)且表索引少的情況下。這種方式最簡單,先插入數據再建索引的效率也是最高。但是風險大大,回滾成本高。更多情況下建議還是 先把表和索引都建好,然后按聚集索引一個批次一個批次插入數據。后面主要講這種方式。重點講大表遷移。


    P3:服務器性能和負載

  為什么我把服務器性能和負載排第3,因為服務器性能直接決定了數據更新的性能以及對生產環境系統的影響。而數據更新的性能決定了遷移所需的時長。也會影響到執行步驟。一般來說,磁盤IO性能是最主要的考慮因素。先測試數據庫連續寫入10萬\20萬\30萬.. 不同量級的時間,找到服務器所能承受的數據寫入量級,把這個指標作為批次插入數據的大小。IO影響是很大的,SSD和HDD的插入和查詢數據是兩個截然不同的量級。單塊SSD的連續讀可以做到100MB級的連續讀,20MB級的連續寫。而HDD肯定是達不到這個級別的。

  服務器的負載是這台機器的繁忙程度,如果服務器本身硬件資源條件就不是很好,可能是太老機器,然后還要遷移一張億級別的表,很容易影響到生產系統的使用。例如CPU資源,如果表本身是開啟了數據壓縮或者列存儲索引,插入數據的時候就不會觸發並發,一並發就會占用數據庫的線程資源,造成其他的數據庫請求要等待。例如內存資源本身不多,系統又是屬於混合型的系統,會有其他並發的查詢任務,長時間插入數據會申請內存空間,其他的線程會等待內存資源,你會看到很多像PAGE_IO_LATCH類型的等待資源。再例如IO,這才是最可能影響的,因為對於一台數據庫服務器來說,磁盤80%以上的時間使用率都是超過80%的。如果新表的索引和舊表是一個文件組或者一個磁盤下,等於插入動作要付出2倍的IO,查一遍寫入一遍。你會很自然的看到Windows的性能監視器上面磁盤的使用率或者等待隊列是滿的。所以要適當避開服務器最繁忙的時間點,例如選在凌晨到清晨這段時間批次插入數據,白天就暫停。其次,假如是張非常大數據量的表,盡可能把新表和舊表的數據文件分開不同的磁盤存放,盡可能給新表單獨一個文件組存放數據,主要是兩方面的考慮,一是大表是非常占用磁盤空間,新舊表放一個磁盤很可能把磁盤撐爆,即便不撐滿也夠嗆,不利於后面的磁盤空間利用(對於生產環境來說,磁盤可用空間高於30%才是健康)。二來,分開磁盤有利於遷移的效率,讀寫分開。


    P4:表的數據庫ER關系結構

  這里有幾個方面在遷移前要考慮:

1、表有沒有建CDC

  CDC本身不會阻止表字段改數據類型和表重命名,但是如果用新表替換掉舊表,意味着要把表的CDC關閉再重新建。CDC的作用是捕捉數據變化,一般用於ETL,關閉CDC重建會導致中間不可避免的丟失一些數據變化捕捉,這是必須要跟數據使用的一方溝通好。雖然可以把CDC重建放到切換表的事務里面,但是這樣會導致事務過大,不是非不得已不建議這樣做。參數可以查詢[cdc].[change_tables],[cdc].[captured_columns]

 

關閉CDC用存儲過程sys.sp_cdc_disable_table

EXECUTE sys.sp_cdc_disable_table   
    @source_schema = N'',   
    @source_name = N'',  
    @capture_instance = N'';

 

重建CDC用存儲過程

EXEC sys.sp_cdc_enable_table  
    @source_schema = N''  
  , @source_name = N''  
  , @capture_instance = N''  
  , @role_name = NULL
  , @supports_net_changes = 1  
  , @index_name = N''   
  , @captured_column_list = N''   
  , @filegroup_name = N''; 

 

2、表有沒有被數據庫復制

  如果表參與了數據庫復制,是沒辦法重命名表的。數據類型雖然支持修改

3、表有沒有被外鍵引用

  如果表本身被其他表外鍵引用,需要先把其他表的外鍵約束刪除,新表上線后再建外鍵約束。

4、表有沒有觸發器或者在其他表觸發器被使用

  如果表本身有觸發器,新表上線后需要建觸發器。和CDC一樣,這個過程除非作為事務的一部分,否則會有遺漏數據改動的風險。

5、表有沒有被架構綁定(視圖、函數、存儲過程)

  如果表本身被作為視圖\視圖\存儲過程的一部分架構綁定(WITH SCHEMA_BINDING),重命名表會報出15336的錯誤,需要通過下面的腳本(查找數據庫表\字段被哪些編程對象架構綁定了(SHEMA_BINDING))把涉及架構綁定的對象找出來,然后解除架構綁定后重命名表,再修改回架構綁定。

 

6、數據庫有沒有創建數據庫鏡像

(數據庫快照是不影響表重命名的,例如A表重命名成B表,但是數據庫快照里表還是叫A)。只不過用這個快照的腳本要注意表名,否則會報錯。

 
    P5:數據庫版本和表索引結構

 

 

大表數據遷移的技術實現方法:

這里只講千萬級以上的大表的數據遷移,也就是替換表。講到技術實現方法其實有非常多,當時我接到這個任務的時候其實已經腦海里想到不下3種實現方法,每種方法優缺點都不一樣,而且最重要的是,你的客戶\老板看重的是表數據的可用性還是數據一致性。

 

可用性:秒級甚至毫秒級無感切換到新的表,數據的一致性可以稍微放寬,可以切換完表再去補全尾部的更新數據。

數據一致性:等同於事務交易,要求不能出現任何的差錯,允許有一定的時間窗口。但是一旦切換表,要求切換的瞬間數據必須跟舊表保持絕對一致。

 

第1種: 數據庫快照(Database Snapshot)+ Timestamp更新時間戳 + MERGE INTO + SP_RENAME

利用數據快照初始化數據的優勢是代碼量少,由於數據庫快照的文件是稀疏文件,對磁盤空間的實際占用少,對初始化數據的時候比較友好,不會堵塞源表。缺點是要用到數據級別的DDL,對於已經啟用了AlwaysOn高可用的環境不建議。因為在我經歷過程中出現了后面刪除某個數據庫快照導致了高可用副本failover的情況。

首先比較建議是在初始化數據的時候是采用批量插入數據到新表,通過對聚集索引(通常都是ID),例如ID>=0 AND ID<=500000這樣子去循環去插入數據,把數據寫入的事務盡量不要過大,這個數量根據自己服務器的承載(主要是磁盤性能)來調整,可以慢慢一點點調整,比如先10w\20w\30w\40w\50w... 這樣找到比較合適的批次數量大小。不要一次性就500w 1000w這樣,數據量大如果出現問題,整個回滾的時間是不可預估的。而且過程非常消耗內存資源,會影響其他數據庫的操作,盡量保持事務內數據量的大小可控。

這里還講到了一個 Timestamp更新時間戳,Timestamp主要用在ETL同步,是以前比較老的數據同步方案,對表新增一個數據類型為timestamp的字段,並添加索引。timestamp本身不是一個基礎數據類型,它以binary(8)數據類型存儲從全局變量@@DB_TS獲取到的值,這個全局變量每次數據增刪改都會觸發數值變化,所以它是並非能被轉成時間或者日期,但是它可以被轉成BIGINT。轉成BIGINT你會觀察到它是數值增長的。以前我一篇博文就講過這個東西(基於表TIMESTAMP類型字段+NOLOCK臟讀的ETL增量同步方案發現的數據遺漏問題)。這里不細說。就說Timestamp,如果表本身提供了這個數據類型且有索引的情況,那就非常利於最后尾部更新數據的同步再完成整個新舊表的切換。如果沒有timestamp或者沒有索引的情況下,這個方案就坐不了,因為后面初始化完要做增量,必須要求得能有字段提供增量查詢。那就直接選擇下面的其他方案就行了。

最后SP_RENAME就簡單了,開啟事務,然后補尾部數據,然后直接兩次重命名表,然后提交事務。對離線時間有要求的,盡可能讓事務時間短,保持簡單的邏輯。做之前要對事務每個語句的執行時間有心理預期。這個原則適用於任何一種方案。

 

 

下面是步驟:

步驟1:創建文件組

步驟2:創建分區函數和方案

步驟3:創建新表

步驟4:創建數據庫快照並初始化新表的數據

步驟5:創建新表的索引和觸發器

步驟6:增量同步初始化以后發生改變以及新增的數據(新的快照)

步驟7:增量同步尾部的數據並用重命名新表

 

第2種:CDC(Change Data Capture) + MERGE INTO +  SP_RENAME

CDC就不用多說了,我記得是SQL SERVER 2008版本以后才有的CDC,而且應該它是所有SQL SERVER的數據庫版本都支持的(2008以后的版本),包括Linux 2017和2019也支持了CDC。技術上我覺得比較成熟,非常契合數據更新。之前有篇博文也寫了關於數據遷移的事情(談表數據遷移時,先建索引再插入數據,還是先插入數據再建索引的問題)。

步驟1:Day1 創建新表

步驟2:Day1 創建新表的索引和觸發器

步驟3:Day1 創建舊表CDC並50w一個批次初始化新表的數據

步驟4.1:Day2 從CDC表數據增量MERGE數據到新表

步驟4.2:Day2 從CDC表數據增量再從上次增量往后增量MERGE數據到新表

步驟5:Day2 增量同步尾部的數據MERGE並用重命名新表

 

第3種:TIMESTAMP + MERGE INTO + SP_RENAME

這種就比較純粹,就是TIMESTAMP增量MERGE,但是要求TIMESTAMP字段是加了索引,否則后面做增量的時候沒辦法快速查出數據。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM