1. 程式人生 > >SQL Server 大資料搬遷之檔案組備份還原實戰

SQL Server 大資料搬遷之檔案組備份還原實戰

一.本文所涉及的內容(Contents)

二.背景(Contexts)

  有一個數據庫大概在700G左右,需要從伺服器A搬遷到伺服器B,兩臺伺服器網路傳輸速度可以達到8MB/s,怎麼做才能更快的搬遷並且宕機時間最短呢?

  資料庫業務邏輯概述:這個資料庫只會插入資料,每天大概有300W條資料,不會對資料進行修改,只有一個表比較大,並且這個表是以自增ID作為分割槽依據列的,檔案組會被重用,資料庫為簡單恢復模式,我定時會對錶資料進行交換分割槽刪除資料;

三.解決方案(Solution)

之前我也寫過關於搬遷資料庫的一些文章:

  1. SQL Server 資料庫最小宕機遷移方案,這篇文章是通過完全備份+差異備份的方式遷移資料庫的,這種方式比較合適資料庫只有20G左右的資料庫,宕機時間=差異備份時間+傳輸差異備份時間+還原差異備份時間,一般來說這個時間都比較短,因為差異備份都不會太大;

  2. SQL Server 資料庫遷移偏方,這篇文章是通過作業的方式遷移資料庫的,一個事務中轉移N條(大約2W條)資料,N值可以通過測試進行調整(需要看網路情況而定),這種方式比較適合資料庫比較大,比如幾百G的資料庫,而且網路環境比較差的情況下,宕機時間≈0(當轉移最後一部分資料足夠小),缺點就是遷移的時間會比較長;

  3. 那麼這篇文章我們再來講講其它方式的遷移,在上面提到的背景下,可以通過對分割槽檔案組進行備份的方式遷移資料庫,這種方式比較適合大資料庫的遷移,宕機時間=最後一個檔案組備份時間+傳輸備份時間+還原最後一個檔案組時間,缺點是宕機時間會比較大,但是整體遷移的時間會比較小;下面是邏輯結構圖:

F1_檔案組搬遷邏輯圖

(Figure1:檔案組搬遷邏輯圖)

四.搬遷步驟(Procedure)

  在講述搬遷步驟之前,我們首先來看看檔案組的大體情況,通過下面的SQL語句可以檢視檔案組的相關資訊,見Figure2、Figure3;

--檢視檔案組資訊
SELECT df.[name], df.physical_name, df.[size], df.growth, fg.[name]
[filegroup], fg.is_default
FROM sys.database_files df
JOIN sys.filegroups fg
ON df.data_space_id = fg.data_space_id

F2_fg1

(Figure2:檔案組列表)

F3_fg2

(Figure3:檔案組列表)

下面就講講搬遷的步驟:

1. 首先我們先清理下資料,把不必要的資料通過交換分割槽的方式交換出去;

2. 檢視這張大表當前的自增ID值,通過修改分割槽方案讓新插入的資料存入到一個空的檔案組(因為空的檔案組在最後備份會更小一點),很多情況下,檔案組是會重用的,所以要注意這個檔案組是空的;

3. 設定資料庫為完整恢復模式;

4. 備份除了上面提到的檔案組,如果條件允許可以進行備份的壓縮;(動態生成SQL)

5. 通過FTP傳輸備份檔案到新的伺服器;

6. 備份主分割槽,需要確保這個時候不會對主分割槽的資料進行修改,並傳輸主分割槽備份檔案;

7. 先還原主分割槽的備份,再還原上面的檔案組備份;(動態生成SQL)

8. 對最後一個檔案組進行備份,對日誌進行備份,對沒有做分割槽對齊的索引檔案組進行備份,把這3個備份傳輸到新伺服器;

9. 還原檔案組,還原日誌;

五.搬遷指令碼(SQL Codes)

搬遷指令碼包括兩個部分,一個備份使用的指令碼,一個是還原使用的指令碼:

1. 備份指令碼,根據分割槽情況來自動生成對應的備份指令碼;

2. 還原指令碼,根據分割槽情況和備份檔案的規則來生成對應的還原指令碼,也就是說還原指令碼是依據備份指令碼的;

(一) 下面是用於生成備份SQL的程式碼,這個程式碼需要提供兩個變數值:

1. @DataBaseName指定需要進行備份的資料庫名,值為'Barefoot.Archives';

2. @BackupPath在舊伺服器本地備份檔案組存放的地址,值為:'E:\DBBackup\';

在舊資料庫Barefoot.Archives中執行下面的SQL指令碼:

-- =============================================
-- Author:      <聽風吹雨>
-- Blog:        <http://gaizai.cnblogs.com/>
-- Create date: <2014/02/26>
-- Description: <生成分割槽備份指令碼>
-- =============================================
DECLARE @DataBaseName SYSNAME--資料庫名稱
DECLARE @BackupPath SYSNAME--儲存分割槽備份的路徑
DECLARE @FilegroupName SYSNAME--分割槽檔案組名稱
DECLARE @sql NVARCHAR(MAX)--sql字串

--設定下面變數
SET @DataBaseName = 'DataBaseName'
SET @BackupPath = 'D:\DBBackup\'

--1.設定完整模式
PRINT '--設定完整模式'
SET @sql = 'USE [master]
GO
ALTER DATABASE ['+@DataBaseName +'] SET RECOVERY FULL WITH NO_WAIT
GO'
PRINT @sql + CHAR(13)

--2.備份分割槽
DECLARE @itemCur CURSOR
SET @itemCur = CURSOR FOR
    SELECT [name] FROM sys.filegroups ORDER BY is_default

OPEN @itemCur
FETCH NEXT FROM @itemCur INTO @FilegroupName
WHILE @@FETCH_STATUS=0
BEGIN
    --邏輯處理
    PRINT '--備份分割槽- ' + @FilegroupName
    SET @sql = 'BACKUP DATABASE [' + @DataBaseName + ']
FILEGROUP = ''' + @FilegroupName + '''
TO DISK = ''' + @BackupPath+@FilegroupName + '.bak'' WITH FORMAT
GO'
    PRINT @sql + CHAR(13)
    
    FETCH NEXT FROM @itemCur INTO @FilegroupName
END 

CLOSE @itemCur
DEALLOCATE @itemCur

--3.備份日誌
PRINT '--備份日誌'
SET @sql = 'BACKUP LOG [' + @DataBaseName + ']
TO DISK = ''' + @BackupPath+@DataBaseName + '_Log.bak'' WITH FORMAT
GO'
PRINT @sql + CHAR(13)

上面SQL指令碼的邏輯是:

1. 首先設定資料庫的恢復模式為完整恢復模式,這是為了後面對資料庫的日誌進行備份;

2. 通過當前資料庫的系統表sys.filegroups拿到檔案組的名稱,這裡把預設檔案排在最後面,這是因為有可能會對配置表進行的操作,所以把這個檔案組放到最後備份;

3. 使用遊標的方式來迴圈檔案組,生成檔案組對應的備份SQL語句;

4. 最後備份資料庫的日誌,對檔案組的還原是需要通過日誌備份才能還原的;

  在舊資料庫執行上面的SQL指令碼,將會產生生成下面的SQL(只保留了部分SQL):

--設定完整模式
USE [master]
GO
ALTER DATABASE [DataBaseName] SET RECOVERY FULL WITH NO_WAIT
GO

--備份分割槽- FG_Archive_Id_01
BACKUP DATABASE [DataBaseName]
FILEGROUP = 'FG_Archive_Id_01'
TO DISK = 'D:\DBBackup\FG_Archive_Id_01.bak' WITH FORMAT
GO

--備份分割槽- FG_Archive_Id_02
BACKUP DATABASE [DataBaseName]
FILEGROUP = 'FG_Archive_Id_02'
TO DISK = 'D:\DBBackup\FG_Archive_Id_02.bak' WITH FORMAT
GO

--備份分割槽- FG_Archive_Index
BACKUP DATABASE [DataBaseName]
FILEGROUP = 'FG_Archive_Index'
TO DISK = 'D:\DBBackup\FG_Archive_Index.bak' WITH FORMAT
GO

--備份分割槽- PRIMARY
BACKUP DATABASE [DataBaseName]
FILEGROUP = 'PRIMARY'
TO DISK = 'D:\DBBackup\PRIMARY.bak' WITH FORMAT
GO

--備份日誌
BACKUP LOG [DataBaseName]
TO DISK = 'D:\DBBackup\Barefoot.Archives_Log.bak' WITH FORMAT
GO

執行完上面的指令碼,會生成下圖所示的備份檔案:

F4_備份檔案列表

(Figure4:備份檔案列表)

(二) 下面是用於生成還原SQL的程式碼,這個程式碼需要提供幾個變數值:

1. @DataBaseName指定需要進行備份的資料庫名,值為'Barefoot.Archives';

2. @BackupPath在新伺服器檔案組備份的地址,值為:'E:\DBBackup\';

3. @SavePath_Drive存在資料檔案的碟符,值為:'F:\';

4. @SavePath_FolderName存放資料檔案的資料夾,值為:'DataBase\';

5. @SavePath_SubFolderName存放ndf檔案的資料夾,值為:'FG_Archive\';

6. @IsSamePath表示是否延續之前的physical_name值,值為1表示延續,這樣會使用@SavePath_Drive替換physical_name的碟符,這樣@SavePath_FolderName和@SavePath_SubFolderName就不會起作用了,值為0表示不延續,這樣physical_name的值[email protected][email protected][email protected]_SubFolderName;

在舊資料庫Barefoot.Archives中執行下面的SQL指令碼:

-- =============================================
-- Author:      <聽風吹雨>
-- Blog:        <http://gaizai.cnblogs.com/>
-- Create date: <2014/02/26>
-- Description: <生成分割槽還原指令碼>
-- =============================================
DECLARE @DataBaseName SYSNAME--資料庫名稱
DECLARE @BackupPath SYSNAME--儲存備份檔案的路徑
DECLARE @SavePath_Drive SYSNAME--儲存資料庫檔案的碟符
DECLARE @SavePath_FolderName SYSNAME--儲存資料庫的資料夾
DECLARE @SavePath_SubFolderName SYSNAME--儲存分割槽的資料夾
DECLARE @FilegroupName SYSNAME--分割槽檔案組名稱
DECLARE @FileName SYSNAME--分割槽檔名稱
DECLARE @PhysicalName SYSNAME--物理路徑
DECLARE @IsSamePath INT--是否跟遠路徑一樣1,0
DECLARE @sql NVARCHAR(MAX)--sql字串

--設定下面變數
SET @DataBaseName = 'DataBaseName'
SET @BackupPath = 'E:\DBBackup\'
SET @SavePath_Drive = 'F:\'
SET @SavePath_FolderName = 'DataBase\'
SET @SavePath_SubFolderName = 'FG_Archive\'
SET @IsSamePath = 1

--1.還原主分割槽
SELECT @FilegroupName = [name] FROM sys.filegroups WHERE is_default = 1
PRINT '--還原主分割槽'
SET @sql = 'RESTORE DATABASE [' + @DataBaseName + ']
FILEGROUP = ''' + @FilegroupName + '''
FROM DISK = ''' + @BackupPath + @FilegroupName + '.bak'' WITH FILE = 1, 
MOVE N''' + @DataBaseName + ''' TO N''' + @SavePath_Drive + @SavePath_FolderName + @DataBaseName + '.mdf'',  
MOVE N''' + @DataBaseName + '_log'' TO N''' + @SavePath_Drive + @SavePath_FolderName + @DataBaseName + '_log.ldf'',
NORECOVERY,REPLACE,STATS = 10
GO'
PRINT @sql + CHAR(13)

--2.還原分割槽
DECLARE @itemCur CURSOR
SET @itemCur = CURSOR FOR
    SELECT df.[name] AS FileName, df.physical_name, fg.[name] AS FilegroupName
        FROM sys.database_files df
        JOIN sys.filegroups fg
        ON df.data_space_id = fg.data_space_id
     WHERE fg.is_default = 0

OPEN @itemCur
FETCH NEXT FROM @itemCur INTO @FileName,@PhysicalName,@FilegroupName
WHILE @@FETCH_STATUS=0
BEGIN
    --邏輯處理
    PRINT '--還原分割槽- ' + @FilegroupName
    IF @IsSamePath = 0
        SET @PhysicalName = @SavePath_Drive + @SavePath_FolderName + @SavePath_SubFolderName + '\' + @FileName + '.ndf'
    ELSE
        SET @PhysicalName = @SavePath_Drive + SUBSTRING(@PhysicalName,CHARINDEX('\',@PhysicalName)+1,LEN(@PhysicalName))
    SET @sql = 'RESTORE DATABASE [' + @DataBaseName + ']
FILEGROUP = ''' + @FilegroupName + '''
FROM DISK = ''' + @BackupPath+@FilegroupName + '.bak'' WITH FILE = 1, 
MOVE N''' + @FileName + ''' 
TO N''' + @PhysicalName + ''',
NORECOVERY
GO'
    PRINT @sql + CHAR(13)
    
    FETCH NEXT FROM @itemCur INTO @FileName,@PhysicalName,@FilegroupName
END 

CLOSE @itemCur
DEALLOCATE @itemCur

--3.還原日誌
PRINT '--還原日誌'
SET @sql = 'RESTORE LOG [' + @DataBaseName + ']
FROM DISK = ''' + @BackupPath + @DataBaseName + '_Log.bak''
WITH NORECOVERY
GO'
PRINT @sql + CHAR(13)

--4.還原線上
PRINT '--還原線上'
SET @sql = 'RESTORE DATABASE [' + @DataBaseName + ']
WITH RECOVERY
GO'
PRINT @sql + CHAR(13)

上面SQL指令碼的邏輯是:

1. 通過系統表sys.filegroups找到預設檔案組,先還原這個主檔案;

2. 使用遊標的方式來迴圈系統表sys.filegroups,拿到檔案組名稱,生成檔案組對應的還原SQL語句;

3. 接著還原資料庫的日誌;

4. 最後還原線上,讓資料庫線上;

執行上面的SQL指令碼,將會產生生成下面的SQL(只保留了部分SQL):

--還原主分割槽
RESTORE DATABASE [DataBaseName]
FILEGROUP = 'PRIMARY'
FROM DISK = 'E:\DBBackup\PRIMARY.bak' WITH FILE = 1, 
MOVE N'Barefoot.Archives' TO N'F:\DataBase\Barefoot.Archives.mdf',  
MOVE N'Barefoot.Archives_log' TO N'F:\DataBase\Barefoot.Archives_log.ldf',
NORECOVERY,REPLACE,STATS = 10
GO

--還原分割槽- FG_Archive_Id_01
RESTORE DATABASE [DataBaseName]
FILEGROUP = 'FG_Archive_Id_01'
FROM DISK = 'E:\DBBackup\FG_Archive_Id_01.bak' WITH FILE = 1, 
MOVE N'FG_Archive_Id_01_data' 
TO N'F:\DataBase\FG_Archive\FG_Archive_Id_01_data.ndf',
NORECOVERY
GO

--還原分割槽- FG_Archive_Id_02
RESTORE DATABASE [DataBaseName]
FILEGROUP = 'FG_Archive_Id_02'
FROM DISK = 'E:\DBBackup\FG_Archive_Id_02.bak' WITH FILE = 1, 
MOVE N'FG_Archive_Id_02_data' 
TO N'F:\DataBase\FG_Archive\FG_Archive_Id_02_data.ndf',
NORECOVERY
GO

--還原分割槽- FG_Archive_Index
RESTORE DATABASE [DataBaseName]
FILEGROUP = 'FG_Archive_Index'
FROM DISK = 'E:\DBBackup\FG_Archive_Index.bak' WITH FILE = 1, 
MOVE N'FG_Archive_Index_data' 
TO N'F:\DataBase\Barefoot.Archives\FG_Archive_Index_data.ndf',
NORECOVERY
GO

--還原日誌
RESTORE LOG [DataBaseName]
FROM DISK = 'E:\DBBackup\Barefoot.Archives_Log.bak'
WITH NORECOVERY
GO

--還原線上
RESTORE DATABASE [DataBaseName]
WITH RECOVERY
GO

在新伺服器上執行上面的SQL指令碼還原資料庫,需要注意的是:在還原線上之前資料庫都是一直處於:正在還原的狀態的;

六.注意事項(Attention)

1. 在實際運用中,可以結合本文和SQL Server 資料庫遷移偏方進行靈活結合運用,當通過本檔案組備份後,舊庫繼續進資料,在花銷時間最大的網路傳輸過程和還原過程繼續對老庫進資料,這樣當還原好資料庫之後使用SQL Server 資料庫遷移偏方來轉移最新的資料,這樣宕機的時間會趨向於0;

2. 其實為了確保某些檔案組不被修改,可以設定檔案組的只讀屬性,這樣可以確保只有某個檔案組在進新資料,可惜的是設定了只讀也無法拷貝這些檔案組檔案通過FTP傳輸,提示:操作無法完成,因為檔案已在SQL Server(MSSQLSERVER)中開啟。

3. 上面指令碼的每個檔案組中只包含了一個檔案,如果一個檔案組包含多個檔案,那就需要修改下指令碼了;

4. 高文佳曾經說過,可以先刪除索引,再壓縮備份,還原之後再建立索引,是的,這不防是一個好方法,不過需要考慮兩點,一個是在還原之後建立索引的速度與時間,如果磁碟速度不算快,那你就要考慮刪除索引是否適合了;另外一點是你的資料庫是否能停機讓你刪除索引,這個跟具體的業務有關;

七.疑問(Questions)

  1. 對primary進行完整檔案組備份(作為基備份),對FG1進行完整檔案組備份(作為基備份)這些描述有問題吧?對primary進行完整檔案組備份應該不會生成基線的吧? SQL檔案組備份和還原

  2. 如果在同一個檔案組中有兩個以上的分割槽值,就是把兩個段的分割槽方案中同指向同一個分割槽檔案組,那在備份和還原有什麼需要注意的呢?能成功備份還原嘛?

--備份分割槽
DECLARE @FileName VARCHAR(200)
SET @FileName = 'G:\DBBackup\FG_Archive_Id_05_null.bak'
BACKUP DATABASE [DataBaseName]
FILEGROUP='FG_Archive_Id_05' TO DISK=@FileName WITH FORMAT
GO

--還原分割槽
RESTORE DATABASE [DataBaseName]
FILEGROUP='FG_Archive_Id_05' FROM DISK='E:\DBBackup\FG_Archive_Id_05_null.bak' WITH  FILE = 1, 
MOVE N'FG_Archive_Id_05_data' TO N'E:\DataBase\FG_Archive\FG_Archive_Id_05_data.ndf',  
NORECOVERY
GO

  解答:從備份和還原的程式碼可以看出只是把FILEGROUP與bak對應,與ndf檔案對應,所以是不需要理會這個檔案組中包含了多少個邏輯分割槽;

八.參考文獻(References)