一.本文所涉及的內容(Contents)
- 本文所涉及的內容(Contents)
- 背景(Contexts)
- 案例分析(Case)
- 實現代碼(SQL Codes)
- 主分區完整、差異還原(Primary Backup And Restore)
- 參考文獻(References)
二.背景(Contexts)
在我的數據庫實例中,有很多下圖所示的數據庫,這些數據庫的名稱是有規律的,每個數據庫包含的表都是相同的,其中2個表是類似流水記錄的表,表的數據量會比較大,占用的空間有幾十G到上百G不等,這2個表相對於其它的配置表來說是比較不重要的。
現在有一個需求就是對數據庫進行備份,允許丟失這兩個表的數據,保留重要的配置表數據,你是否遇到過同樣的問題呢?這個時候你會怎么做呢?你有什么方案呢?有什么方法可以快速備份這些數據庫呢?
(Figure1:數據庫列表)
閱讀本文之前你可以先參考:SQL Server 批量完整備份
三.案例分析(Case)
通過上面的描述,其中很重要的一點就是每個數據庫中有2個大表,而且這些數據是不重要的,那么我們對這2個大表做表分區,把大數據放到其它文件組中,只留重要的配置表在主文件組(PRIMARY)中,接着就可以對主文件組進行備份,這樣既滿足了備份重要表數據,而且不會造成備份文件過大、占用磁盤空間、備份時間過長等問題。
確定了方向之后我們接着考慮作業的問題,通過作業備份類似Figure1的數據庫,有下面兩種方案可供選擇:
(Figure2:作業列表)
(Figure3:作業列表)
Figure2是一個方案,這些作業是可以自動化創建,但是會用到批處理,代碼會復雜一點,唯一的缺點就是當新創建了數據庫,是無法自動備份的(不過可以通過專門創建一個監控數據庫的新建、刪除狀態的Job來解決這個問題)詳情請參考:SQL Server 批量主分區備份(Multiple Jobs)
Figure3就是我們這篇文章需要講述的方案,這里把所有的數據庫的備份都集中到一個作業中,這個方案有以下缺點:
1) 整個備份過程是串行的;
2) 如果沒有異常處理,那么后面的數據庫就沒有辦法備份了;
3) 在作業執行的時候對服務器壓力比較大(沒有分散執行時間);
4) 做本身的msdb數據庫中記錄的作業日志也沒有那么清晰,排錯比較困難(只有整個作業的信息,msdb.dbo.sysjobhistory的message字段保存不了太多信息),需要自己創建表進行記錄;
盡管有以上的缺點,但是也是有優點的:當新創建了一個類似的數據庫(業務需要),這個時候作業也會備份這個數據,不用人工去創建作業;另外還有一個,就是當數據庫多而小的時候,這個方案特別有用;下面就來講講這個Job實現的具體步驟。
四.實現代碼(SQL Codes)
實現步驟概要:
1. 批量創建文件夾;
2. 創建維護表:[JobLog]和[ErrorLog];
3. 創建備份所有數據庫的SQL腳本;
4. 創建Job執行上面的腳本;
(一) 為了方便管理備份文件,我們為每個數據庫創建單獨的文件夾,下面的SQL代碼實現根據數據庫批量創建數據庫名對應的文件夾,使用了游標循環數據庫名進行創建文件夾,執行cmd命令需要開啟數據庫的xp_cmdshell開關;
--批量創建文件夾 EXEC sp_configure 'show advanced options', 1 RECONFIGURE EXEC sp_configure 'xp_cmdshell', 1 RECONFIGURE DECLARE @DBName VARCHAR(100) DECLARE @SQL VARCHAR(1000) DECLARE CurDBName CURSOR FOR SELECT name FROM sys.databases WHERE name LIKE '%Opinion%' AND STATE =0 OPEN CurDBName FETCH NEXT FROM CurDBName INTO @DBName WHILE @@FETCH_STATUS = 0 BEGIN SET @SQL = 'mkdir E:\DBBackup\' + @DBName EXEC xp_cmdshell @SQL FETCH NEXT FROM CurDBName INTO @DBName END CLOSE CurDBName DEALLOCATE CurDBName EXEC sp_configure 'show advanced options', 0 RECONFIGURE EXEC sp_configure 'xp_cmdshell', 0 RECONFIGURE
執行上面的腳本后,會在E:\DBBackup\目錄下創建如下圖所示的文件夾:
(Figure4:創建的文件夾)
(二) 對備份的維護,我希望可以了解到所有數據庫的備份情況,所以下面創建2個維護表:[JobLog]和[ErrorLog],這兩個表用於記錄作業的執行情況,通過這兩個表,可以實現如Figure5、Figure6的效果;
--作業記錄表 USE [msdb] GO CREATE TABLE [dbo].[JobLog]( [Id] [int] IDENTITY(1,1) NOT NULL, [DB_Name] [varchar](50) NULL, [Backup_Date] [int] NULL, [Backup_Time] [int] NULL, [Backup_Duration] [int] NULL, [Backup_Type] [char](4) NULL, CONSTRAINT [PK_JobLog] PRIMARY KEY CLUSTERED ( [Id] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] --錯誤記錄表 USE [msdb] GO CREATE TABLE [dbo].[ErrorLog]( [Id] [int] IDENTITY(1,1) NOT NULL, [DB_Name] [varchar](50) NOT NULL, [Backup_Time] [datetime] NOT NULL CONSTRAINT [DF_ErrorLog_Backup_Time] DEFAULT (getdate()), [Messages] [nvarchar](500) NULL, CONSTRAINT [PK_ErrorLog] PRIMARY KEY CLUSTERED ( [Id] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY]
(三) 下面的代碼實現了主分區完整備份和主分區差異備份(主分區可參考:SQL Server 維護計划備份主分區),當是星期一的深夜的時候,我們做完整備份,如果是其它時候我們就做差異備份,具體是什么時候,這個就通過計划里面的時候來控制了(計划的執行時間為星期一、星期三、星期五,這就代表星期一深夜做了完整備份、星期三和星期五分別做了差異備份)。(備份實踐可參考:SQL Server 2008 維護計划實現數據庫備份)
下面是生成的備份文件命名的范例,這樣的備份文件的命名可以方便維護,而且直觀知道備份文件創建的時間,可以精確到秒,文件名重復的幾率不大;
DBName _Primary_Full_2013_01_14_002007.bak
DBName_Primary_Diff_2013_01_16_002034.bak
下面是整個批量備份數據庫的核心SQL腳本,如果你是創建維護計划,那可以把這個SQL放到“執行 T-SQL 語句”任務,如果是創建Job的,可以放到作業的步驟里;
--批量備份數據庫 DECLARE @DBName VARCHAR(100) DECLARE @CurrentTime VARCHAR(50) DECLARE @FileName VARCHAR(200) DECLARE @WithType CHAR(20) DECLARE @Backup_Date VARCHAR(50) DECLARE @Backup_Time VARCHAR(50) DECLARE @Backup_Duration VARCHAR(50) DECLARE @Backup_Start DATETIME DECLARE @Backup_End DATETIME DECLARE @BackupType CHAR(4) DECLARE @SQL VARCHAR(MAX) --防止作業遺漏備份 INSERT INTO [msdb].[dbo].[JobLog]([DB_Name],[Backup_Date],[Backup_Time],[Backup_Duration],[Backup_Type]) SELECT name,0,0,0,NULL FROM sys.databases WHERE name LIKE '%Opinion%' AND STATE =0 AND name NOT IN (SELECT DISTINCT [DB_Name] FROM [msdb].[dbo].[JobLog]) ORDER BY name DECLARE CurDBName CURSOR FOR SELECT name FROM sys.databases WHERE name LIKE '%Opinion%' AND STATE =0 ORDER BY name OPEN CurDBName FETCH NEXT FROM CurDBName INTO @DBName WHILE @@FETCH_STATUS = 0 BEGIN --Execute Backup --捕獲異常 BEGIN TRY PRINT @DBName SET @CurrentTime = REPLACE(REPLACE(REPLACE(CONVERT(VARCHAR, GETDATE(), 120 ),'-','_'),' ','_'),':','') IF(DATEPART(DW, GETDATE()) = 2)--星期一 BEGIN SET @FileName = 'E:\DBBackup\'+@DBName+'\'+@DBName+'_Primary_Full_' + @CurrentTime+'.bak' SET @WithType = ' FORMAT' SET @BackupType = 'Full' END ELSE BEGIN SET @FileName = 'E:\DBBackup\'+@DBName+'\'+@DBName+'_Primary_Diff_' + @CurrentTime+'.bak' SET @WithType = ' DIFFERENTIAL,FORMAT' SET @BackupType = 'Diff' END SET @Backup_Start = GETDATE() SET @SQL = ' --1設置完整模式 ALTER DATABASE ['+@DBName+'] SET RECOVERY FULL WITH NO_WAIT; --2備份主分區 BACKUP DATABASE ['+@DBName+'] FILEGROUP=''PRIMARY'' TO DISK='''+@FileName+''' WITH '+@WithType+'; --3設置簡單模式 ALTER DATABASE ['+@DBName+'] SET RECOVERY SIMPLE WITH NO_WAIT; ' EXEC(@SQL) SET @Backup_End = GETDATE() SET @Backup_Date = CONVERT(VARCHAR, GETDATE(),112) SET @Backup_Time = REPLACE(CONVERT(VARCHAR, GETDATE(),24),':','') SET @Backup_Duration = CONVERT(VARCHAR,DATEDIFF(ss,@Backup_Start,@Backup_End)) PRINT @Backup_Date +@Backup_Time +@Backup_Duration SET @SQL = ' INSERT INTO [msdb].[dbo].[JobLog]([DB_Name],[Backup_Date],[Backup_Time],[Backup_Duration],[Backup_Type]) VALUES('''+@DBName+''','+@Backup_Date+','+@Backup_Time+','+@Backup_Duration+','''+@BackupType+'''); ' EXEC(@SQL) END TRY BEGIN CATCH INSERT INTO [dbo].[ErrorLog]([DB_Name],[ErrorMessage]) VALUES(@DBName,ERROR_MESSAGE()) --ROLLBACK TRANSACTION END CATCH --Get Next DataBase FETCH NEXT FROM CurDBName INTO @DBName END CLOSE CurDBName DEALLOCATE CurDBName
這個備份腳本中使用了游標循環獲取數據庫名進行備份,在【--防止作業遺漏備份】標簽的SQL語句是為了保證記錄表[JobLog]每次執行都有新的記錄,即使備份失敗(如何查看后面會講到)也可以觀察到對應的記錄;
腳本中加入了異常處理,可以有效的防止某個數據庫備份失敗后,后面數據庫的備份不受影響,把異常信息插入到[ErrorLog]。
SQL代碼里面強制了星期一進行主分區的完整備份,其它什么時候做差異備份,這個就完全由作業中計划來控制(如果你想,你可以通過作業中的計划來調整每天都進行差異備份)。
(四) 下面的代碼實現了刪除備份文件,從下面的代碼實現刪除14天之前的備份文件,這個可以作為第三步驟的下一個步驟,但是需要注意有相應的機制可以檢測到備份失敗的數據庫,不然一段時間備份都失敗了,會造成最后沒有了備份文件(可以通過郵件查詢[ErrorLog]進行預警,可以參考:SQL Server 創建數據庫郵件)
--刪除14天之前的備份文件 DECLARE @DeleteDate DATETIME SET @DeleteDate = DATEADD(DAY, -14, GETDATE()) EXECUTE MASTER.SYS.XP_DELETE_FILE 0, N'E:\DBBackup\', N'bak', @DeleteDate
(五) 查看作業的運行情況;
--行轉列(備份類型) DECLARE @s NVARCHAR(MAX) SET @s='' SELECT @s=@s+','+quotename([Backup_Date])+'=MAX(CASE WHEN [Backup_Date]='+quotename([Backup_Date],'''')+' THEN [Backup_Type] ELSE NULL END)' FROM [msdb].[dbo].[JobLog] GROUP BY [Backup_Date] ORDER BY [Backup_Date] PRINT @s EXEC('SELECT [DB_Name] '+@s+' FROM [msdb].[dbo].[JobLog] GROUP BY [DB_Name] ORDER BY [DB_Name]')
(Figure5:作業備份類型)
--行轉列(執行時間) DECLARE @s NVARCHAR(MAX) SET @s='' SELECT @s=@s+','+quotename([Backup_Date])+'=MAX(CASE WHEN [Backup_Date]='+quotename([Backup_Date],'''')+' THEN [Backup_Duration] ELSE NULL END)' FROM [msdb].[dbo].[JobLog] GROUP BY [Backup_Date] ORDER BY [Backup_Date] PRINT @s EXEC('SELECT [DB_Name] '+@s+' FROM [msdb].[dbo].[JobLog] GROUP BY [DB_Name] ORDER BY [DB_Name]')
(Figure6:作業執行時間)
五.主分區完整、差異還原(Primary Backup And Restore)
既然做了上面主文件組的備份,當然我們需要去測試這個主文件組的還原了,這樣才可以當遇到問題可以快速還原備份文件,達到還原數據的目的;
接下來會在另外一篇文章里面專門講解;
六.參考文獻(References)
sp_update_schedule (Transact-SQL)