MSSQL · 最佳實踐 · 利用文件組實現冷熱數據隔離備份方案

摘要: 摘要 在SQL Server備份專題分享中,前四期咱們分享了:三種常見的數據庫備份、備份策略的制定、如何查找備份鏈以及數據庫的三種恢復模式與備份之間的關係。本次月報咱們分享SQL Server如何利用文件組技術來實現數據庫冷熱數據隔離備份的方案。算法

 

摘要數據庫

 

在SQL Server備份專題分享中,前四期咱們分享了:三種常見的數據庫備份、備份策略的制定、如何查找備份鏈以及數據庫的三種恢復模式與備份之間的關係。本次月報咱們分享SQL Server如何利用文件組技術來實現數據庫冷熱數據隔離備份的方案。負載均衡

 

場景引入數據庫設計

 

假設某公司有一個很是重要的超大的數據庫(超過10TB),面臨以下場景:性能

  • 該數據庫中存儲了近10年的用戶支付信息(payment),很是重要
  • 每一年的數據歸檔存儲在年表中,歷史年表中的數據只讀不寫(歷史payment信息無需再修改),只有當前年表數據既讀又寫
  • 每次數據庫全備耗時太長,超過20小時;數據庫還原操做耗時更長,超過30小時

如何優化設計這個數據庫以及備份恢復系統,可使得備份、還原更加高效?測試

 

文件組簡介大數據

 

文件組的詳細介紹不是本次分享的重點,可是做爲本文介紹的核心技術,有必要對其優勢、建立以及使用方法來簡單介紹SQL Server中的文件組。優化

 

使用文件組的優勢spa

 

SQL Server支持將表、索引數據存放到非Primary文件組,這樣當數據庫擁有多個文件組時就具有了以下好處:設計

  • 分散I/O壓力到不一樣的文件組上,若是不一樣文件組的文件位於不一樣的磁盤的話,能夠分散磁盤壓力。
  • 針對不一樣的文件組進行DBCC CHECKFILEGROUP操做,而且同一個數據庫能夠多個進程並行處理,減小大數據維護時間。
  • 能夠針對文件組級別進行備份和還原操做,更細粒度控制備份和還原策略。

 

建立數據庫時建立文件組

 

咱們能夠在建立數據庫時直接建立文件組,代碼以下:

 

USE master GO EXEC sys.xp_create_subdir 'C:\SQLServer\Data\' EXEC sys.xp_create_subdir 'C:\SQLServer\Logs\' CREATE DATABASE [TestFG] ON PRIMARY ( NAME = N'TestFG', FILENAME = N'C:\SQLServer\Data\TestFG.mdf' , SIZE = 5MB ,FILEGROWTH = 50MB ), FILEGROUP [FG2010] ( NAME = N'FG2010', FILENAME = N'C:\SQLServer\Data\FG2010.ndf' , SIZE = 5MB ,FILEGROWTH = 50MB ), FILEGROUP [FG2011] ( NAME = N'FG2011', FILENAME = N'C:\SQLServer\Data\FG2011.ndf' , SIZE = 5MB ,FILEGROWTH = 50MB ), FILEGROUP [FG2012] ( NAME = N'FG2012', FILENAME = N'C:\SQLServer\Data\FG2012.ndf' , SIZE = 5MB ,FILEGROWTH = 50MB ) LOG ON ( NAME = N'TestFG_log', FILENAME = N'C:\SQLServer\Logs\TestFG_log.ldf' , SIZE = 5MB , FILEGROWTH = 50MB) GO

注意: 爲了保證數據庫文件組I/O的負載均衡能力,請將全部文件的初始大小和自動增加參數保持一致,以保證輪詢調度分配算法正常工做。

 

單首創建建立組

 

若是數據庫已經存在,咱們也一樣有能力添加文件組,代碼以下:

 

--Add filegroup FG2013 USE master GO ALTER DATABASE [TestFG] ADD FILEGROUP [FG2013]; -- Add data file to FG2013 ALTER DATABASE [TestFG] ADD FILE (NAME = FG2013, SIZE = 5MB , FILEGROWTH = 50MB ,FILENAME = N'C:\SQLServer\Data\FG2013.ndf') TO FILEGROUP [FG2013] GO USE [TestFG] GO SELECT * FROM sys.filegroups

最終文件組信息,展現以下: 

 

使用文件組

 

文件組建立完畢後,咱們能夠將表和索引放到對應的文件組。好比: 將彙集索引放到PRIMARY文件組;表和索引數據放到FG2010文件組,代碼以下:

USE [TestFG] GO CREATE TABLE [dbo].[Orders_2010]( [OrderID] [int] IDENTITY(1,1) NOT NULL, [OrderDate] [datetime] NULL, CONSTRAINT [PK_Orders_2010] PRIMARY KEY CLUSTERED ( [OrderID] ASC ) ON [PRIMARY] ) ON [FG2010] GO CREATE NONCLUSTERED INDEX IX_OrderDate ON [dbo].[Orders_2010] (OrderDate) ON [FG2010];

方案設計

 

文件組的基本知識點介紹完畢後,根據場景引入中的內容,咱們將利用SQL Server文件組技術來實現冷熱數據隔離備份的方案設計介紹以下。

 

設計分析

 

因爲payment數據庫過大,超過10TB,單次全量備份超過20小時,若是按照常規的徹底備份,會致使備份文件過大、耗時過長、甚至會由於備份操做對I/O能力的消耗影響到正常業務。咱們仔細想一想會發現,雖然數據庫自己很大,可是,因爲只有當前年表數據會不斷變化(熱數據),歷史年表數據不會修改(冷數據),所以正真有數據變化操做的數據量相對整個庫來看並不大。那麼,咱們將數據庫設計爲歷史年表數據放到Read only的文件組上,把當前年表數據放到Read write的文件組上,備份系統僅僅須要備份Primary和當前年表所在的文件組便可(固然首次仍是須要對數據庫作一次性完整備份的)。這樣既能夠大大節約備份對I/O能力的消耗,又實現了冷熱數據的隔離備份操做,還達到了分散了文件的I/O壓力,最終達到數據庫設計和備份系統優化的目的,可謂一箭多雕。

 

以上文字分析,畫一個漂亮的設計圖出來,直觀展現以下: 

 

設計圖說明

 

如下對設計圖作詳細說明,以便對設計方案有更加直觀和深刻理解。 整個數據庫包含13個文件,包括:

  • 1個主文件組(Primary File Group):用戶存放數據庫系統表、視圖等對象信息,文件組可讀可寫。
  • 10個用戶自定義只讀文件組(User-defined Read Only File Group):用於存放歷史年表的數據及相應索引數據,每年的數據存放到一個文件組中。
  • 1個用戶自定義可讀寫文件組(User-defined Read Write File Group):用於存放當前年表數據和相應索引數據,該表數據必須可讀可寫,因此文件組必須可讀可寫。
  • 1個數據庫事務日誌文件:用於數據庫事務日誌,咱們須要按期備份數據庫事務日誌。

 

方案實現

 

設計方案完成之後,接下來就是方案的集體實現了,具體實現包括:

  • 建立數據庫
  • 建立年表
  • 文件組設置
  • 冷熱備份實現

 

建立數據庫

 

建立數據庫的同時,咱們建立了Primary文件組和2008 ~ 2017的文件組,這裏須要特別提醒,請務必保證全部文件組中文件的初始大小和增加量相同,代碼以下:

 

USE master GO EXEC sys.xp_create_subdir 'C:\DATA\Payment\Data\' EXEC sys.xp_create_subdir 'C:\DATA\Payment\Log\' CREATE DATABASE [Payment] ON PRIMARY ( NAME = N'Payment', FILENAME = N'C:\DATA\Payment\Data\Payment.mdf' , SIZE = 5MB ,FILEGROWTH = 50MB ), FILEGROUP [FGPayment2008] ( NAME = N'FGPayment2008', FILENAME = N'C:\DATA\Payment\Data\Payment_2008.ndf' , SIZE = 5MB ,FILEGROWTH = 50MB ), FILEGROUP [FGPayment2009] ( NAME = N'FGPayment2009', FILENAME = N'C:\DATA\Payment\Data\Payment_2009.ndf' , SIZE = 5MB ,FILEGROWTH = 50MB ), FILEGROUP [FGPayment2010] ( NAME = N'FGPayment2010', FILENAME = N'C:\DATA\Payment\Data\Payment_2010.ndf' , SIZE = 5MB ,FILEGROWTH = 50MB ), FILEGROUP [FGPayment2011] ( NAME = N'FGPayment2011', FILENAME = N'C:\DATA\Payment\Data\Payment_2011.ndf' , SIZE = 5MB ,FILEGROWTH = 50MB ), FILEGROUP [FGPayment2012] ( NAME = N'FGPayment2012', FILENAME = N'C:\DATA\Payment\Data\Payment_2012.ndf' , SIZE = 5MB ,FILEGROWTH = 50MB ), FILEGROUP [FGPayment2013] ( NAME = N'FGPayment2013', FILENAME = N'C:\DATA\Payment\Data\Payment_2013.ndf' , SIZE = 5MB ,FILEGROWTH = 50MB ), FILEGROUP [FGPayment2014] ( NAME = N'FGPayment2014', FILENAME = N'C:\DATA\Payment\Data\Payment_2014.ndf' , SIZE = 5MB ,FILEGROWTH = 50MB ), FILEGROUP [FGPayment2015] ( NAME = N'FGPayment2015', FILENAME = N'C:\DATA\Payment\Data\Payment_2015.ndf' , SIZE = 5MB ,FILEGROWTH = 50MB ), FILEGROUP [FGPayment2016] ( NAME = N'FGPayment2016', FILENAME = N'C:\DATA\Payment\Data\Payment_2016.ndf' , SIZE = 5MB ,FILEGROWTH = 50MB ), FILEGROUP [FGPayment2017] ( NAME = N'FGPayment2017', FILENAME = N'C:\DATA\Payment\Data\Payment_2017.ndf' , SIZE = 5MB ,FILEGROWTH = 50MB ) LOG ON ( NAME = N'Payment_log', FILENAME = N'C:\DATA\Payment\Log\Payment_log.ldf' , SIZE = 5MB , FILEGROWTH = 50MB) GO

考慮到每一年咱們都要添加新的文件組到數據庫中,所以2018年的文件組單首創建以下:

 

--Add filegroup FGPayment2018 USE master GO ALTER DATABASE [Payment] ADD FILEGROUP [FGPayment2018]; -- Add data file to FGPayment2018 ALTER DATABASE [Payment] ADD FILE (NAME = FGPayment2018, SIZE = 5MB , FILEGROWTH = 50MB ,FILENAME = N'C:\DATA\Payment\Data\Payment_2018.ndf') TO FILEGROUP [FGPayment2018] GO

最終再次確認數據庫文件組信息,代碼以下:

 

USE [Payment] GO SELECT file_name = mf.name, filegroup_name = fg.name, mf.physical_name,mf.size,mf.growth FROM sys.master_files AS mf INNER JOIN sys.filegroups as fg ON mf.data_space_id = fg.data_space_id WHERE mf.database_id = db_id('Payment') ORDER BY mf.type;

結果展現以下圖所示: 

 

建立年表

 

數據庫以及相應文件組建立完畢後,接下來咱們建立對應的年表並插入一些測試數據,以下:

USE [Payment] GO CREATE TABLE [dbo].[Payment_2008]( [Payment_ID] [bigint] IDENTITY(12008,100) NOT NULL, [OrderID] [bigint] NOT NULL, CONSTRAINT [PK_Payment_2008] PRIMARY KEY CLUSTERED ( [Payment_ID] ASC ) ON [FGPayment2008] ) ON [FGPayment2008] GO CREATE NONCLUSTERED INDEX IX_OrderID ON [dbo].[Payment_2008] ([OrderID]) ON [FGPayment2008]; CREATE TABLE [dbo].[Payment_2009]( [Payment_ID] [bigint] IDENTITY(12009,100) NOT NULL, [OrderID] [bigint] NOT NULL, CONSTRAINT [PK_Payment_2009] PRIMARY KEY CLUSTERED ( [Payment_ID] ASC ) ON [FGPayment2009] ) ON [FGPayment2009] GO CREATE NONCLUSTERED INDEX IX_OrderID ON [dbo].[Payment_2009] ([OrderID]) ON [FGPayment2009]; --這裏省略了2010-2017的表建立,請參照以上建表和索引代碼,自行補充 CREATE TABLE [dbo].[Payment_2018]( [Payment_ID] [bigint] IDENTITY(12018,100) NOT NULL, [OrderID] [bigint] NOT NULL, CONSTRAINT [PK_Payment_2018] PRIMARY KEY CLUSTERED ( [Payment_ID] ASC ) ON [FGPayment2018] ) ON [FGPayment2018] GO CREATE NONCLUSTERED INDEX IX_OrderID ON [dbo].[Payment_2018] ([OrderID]) ON [FGPayment2018];

這裏須要特別提醒兩點:

  • 限於篇幅,建表代碼中省略了2010 - 2017表建立,請自行補充
  • 每一個年表的Payment_ID字段初始值是不同的,以避免查詢全部payment信息該字段值存在重複的狀況

 

其次,咱們檢查全部年表的文件組分佈狀況以下:

USE [Payment] GO SELECT table_name = tb.[name], index_name = ix.[name], located_filegroup_name = fg.[name] FROM sys.indexes ix INNER JOIN sys.filegroups fg ON ix.data_space_id = fg.data_space_id INNER JOIN sys.tables tb ON ix.[object_id] = tb.[object_id] WHERE ix.data_space_id = fg.data_space_id GO

查詢結果截取其中部分以下,咱們看到全部年表及索引都按照咱們的預期分佈到對應的文件組上去了。 

 

最後,爲了測試,咱們在對應年表中放入一些數據:

 

USE [Payment] GO SET NOCOUNT ON INSERT INTO [Payment_2008] SELECT 2008; INSERT INTO [Payment_2009] SELECT 2009; --省略掉2010 - 2017,自行補充 INSERT INTO [Payment_2018] SELECT 2018;

文件組設置

 

年表建立完完畢、測試數據初始化完成後,接下來,咱們作文件組讀寫屬性的設置,代碼以下:

USE master GO ALTER DATABASE [Payment] MODIFY FILEGROUP [FGPayment2008] READ_ONLY; ALTER DATABASE [Payment] MODIFY FILEGROUP [FGPayment2009] READ_ONLY; --這裏省略了2010 - 2017文件組read only屬性的設置,請自行補充 ALTER DATABASE [Payment] MODIFY FILEGROUP [FGPayment2018] READ_WRITE;

 

最終咱們的文件組讀寫屬性以下:

 

USE [Payment] GO SELECT name, is_default, is_read_only FROM sys.filegroups GO

截圖以下:

 

冷熱備份實現

 

全部文件組建立成功,而且讀寫屬性配置完畢後,咱們須要對數據庫可讀寫文件組進行全量備份、差別備份和數據庫級別的日誌備份,爲了方便測試,咱們會在兩次備份之間插入一條數據。備份操做的大致思路是:

  • 首先,對整個數據庫進行一次性全量備份
  • 其次,對可讀寫文件組進行週期性全量備份
  • 接下來,對可讀寫文件組進行週期性差別備份
  • 最後,對整個數據庫進行週期性事務日誌備份

 

--Take a one time full backup of payment database USE [master]; GO BACKUP DATABASE [Payment] TO DISK = N'C:\DATA\Payment\BACKUP\Payment_20180316_full.bak' WITH COMPRESSION, Stats=5 ; GO -- for testing, init one record USE [Payment]; GO INSERT INTO [dbo].[Payment_2018] SELECT 201801; GO --Take a full backup for each writable filegoup (just backup FGPayment2018 as an example) BACKUP DATABASE [Payment] FILEGROUP = 'FGPayment2018' TO DISK = 'C:\DATA\Payment\BACKUP\Payment_FGPayment2018_20180316_full.bak' WITH COMPRESSION, Stats=5 ; GO -- for testing, insert one record INSERT INTO [dbo].[Payment_2018] SELECT 201802; GO --Take a differential backup for each writable filegoup (just backup FGPayment2018 as an example) BACKUP DATABASE [Payment] FILEGROUP = N'FGPayment2018' TO DISK = N'C:\DATA\Payment\BACKUP\Payment_FGPayment2018_20180316_diff.bak' WITH DIFFERENTIAL, COMPRESSION, Stats=5 ; GO -- for testing, insert one record INSERT INTO [dbo].[Payment_2018] SELECT 201803; GO -- Take a transaction log backup of database payment BACKUP LOG [Payment] TO DISK = 'C:\DATA\Payment\BACKUP\Payment_20180316_log.trn'; GO

這樣備份的好處是,咱們只須要對可讀寫的文件組(FGPayment2018)進行完整和差別備份(Primary中包含系統對象,變化很小,實際場景中,Primary文件組也須要備份),而其餘的9個只讀文件組無需備份,由於數據不會再變化。如此,咱們就實現了冷熱數據隔離備份的方案。 接下來的一個問題是,萬一Payment數據發生災難,致使數據損失,咱們如何從備份集中將數據庫恢復出來呢?咱們能夠按照以下思路來恢復備份集:

  • 首先,還原整個數據庫的一次性全量備份
  • 其次,還原全部可讀寫文件組最後一個全量備份
  • 接下來,還原可讀寫文件組最後一個差別備份
  • 最後,還原整個數據庫的全部事務日誌備份

 

-- We restore full backup USE master GO RESTORE DATABASE [Payment_Dev] FROM DISK=N'C:\DATA\Payment\BACKUP\Payment_20180316_full.bak' WITH MOVE 'Payment' TO 'C:\DATA\Payment_Dev\Data\Payment_dev.mdf', MOVE 'FGPayment2008' TO 'C:\DATA\Payment_Dev\Data\FGPayment2008_dev.ndf', MOVE 'FGPayment2009' TO 'C:\DATA\Payment_Dev\Data\FGPayment2009_dev.ndf', MOVE 'FGPayment2010' TO 'C:\DATA\Payment_Dev\Data\FGPayment2010_dev.ndf', MOVE 'FGPayment2011' TO 'C:\DATA\Payment_Dev\Data\FGPayment2011_dev.ndf', MOVE 'FGPayment2012' TO 'C:\DATA\Payment_Dev\Data\FGPayment2012_dev.ndf', MOVE 'FGPayment2013' TO 'C:\DATA\Payment_Dev\Data\FGPayment2013_dev.ndf', MOVE 'FGPayment2014' TO 'C:\DATA\Payment_Dev\Data\FGPayment2014_dev.ndf', MOVE 'FGPayment2015' TO 'C:\DATA\Payment_Dev\Data\FGPayment2015_dev.ndf', MOVE 'FGPayment2016' TO 'C:\DATA\Payment_Dev\Data\FGPayment2016_dev.ndf', MOVE 'FGPayment2017' TO 'C:\DATA\Payment_Dev\Data\FGPayment2017_dev.ndf', MOVE 'FGPayment2018' TO 'C:\DATA\Payment_Dev\Data\FGPayment2018_dev.ndf', MOVE 'Payment_log' TO 'C:\DATA\Payment_Dev\Log\Payment_dev_log.ldf', NORECOVERY,STATS=5; GO -- restore writable filegroup full backup RESTORE DATABASE [Payment_Dev] FILEGROUP = N'FGPayment2018' FROM DISK = N'C:\DATA\Payment\BACKUP\Payment_FGPayment2018_20180316_full.bak' WITH NORECOVERY,STATS=5; GO -- restore writable filegroup differential backup RESTORE DATABASE [Payment_Dev] FILEGROUP = N'FGPayment2018' FROM DISK = N'C:\DATA\Payment\BACKUP\Payment_FGPayment2018_20180316_diff.bak' WITH NORECOVERY,STATS=5; GO -- restore payment database transaction log backup RESTORE LOG [Payment_Dev] FROM DISK = N'C:\DATA\Payment\BACKUP\\Payment_20180316_log.trn' WITH NORECOVERY; GO -- Take database oneline to check RESTORE DATABASE [Payment_Dev] WITH RECOVERY; GO

 

最後檢查數據還原的結果,按照咱們插入的測試數據,應該會有四條記錄。

 

USE [Payment_Dev] GO SELECT * FROM [dbo].[Payment_2018] WITH(NOLOCK)

展現執行結果,有四條結果集,符合咱們的預期,截圖以下:

 

最後總結

 

本篇月報分享瞭如何利用SQL Server文件組技術來實現和優化冷熱數據隔離備份的方案,在大大提高數據庫備份還原效率的同時,還提供了I/O資源的負載均衡,提高和優化了整個數據庫的性能。

 

原文連接

閱讀更多幹貨好文,請關注掃描如下二維碼: 

相關文章
相關標籤/搜索