一.本文所涉及的內容(Contents)
- 本文所涉及的內容(Contents)
- 背景(Contexts)
- 案例分析(Case)
- 實現代碼(SQL Codes)
- 主分區完整、差異還原(Primary Backup And Restore)
- 參考文獻(References)
二.背景(Contexts)
在我的數據庫實例中,有很多類似下圖所示的數據庫,這些數據庫的名稱是有規律的,每個數據庫包含的表都是相同的,其中2個表是類似流水記錄的表,表的數據量會比較大,占用的空間有幾十G到上百G不等,這2個表相對於其它的配置表來說是比較不重要的。
現在有一個需求就是對數據庫進行備份,允許丟失這兩個表的數據,保留重要的配置表數據,你是否遇到過同樣的問題呢?這個時候你會怎么做呢?你有什么方案呢?有什么方法可以快速備份這些數據庫呢?
(Figure1:數據庫列表)
閱讀本文之前你可以先參考:SQL Server 批量完整備份
三.案例分析(Case)
通過上面的描述,其中很重要的一點就是每個數據庫中有2個大表,而且這些數據是不重要的,那么我們對這2個大表做表分區,把大數據放到其它文件組中,只留重要的配置表在主文件組(PRIMARY)中,接着就可以對主文件組進行備份,這樣既滿足了備份重要表數據,而且不會造成備份文件過大、占用磁盤空間、備份時間過長等問題。
使用維護計划;請參考:SQL Server 維護計划備份主分區,只要在【執行T-SQL語句】的任務中使用循環遞歸所有數據庫進行備份,在【清除歷史記錄】任務和【清除維護】任務中選擇父目錄就可以了;
方案一中通過維護計划生成的作業是看不到具體的備份代碼的,所以方案二就是為了補充方案一的。如果你沒有作業執行時間的特殊要求,你可以創建一個作業,循環數據庫名稱進行主分區的備份,只是把維護計划【執行T-SQL語句】的內容放到了作業中;
這個方案有以下幾個缺點:
a) 沒有辦法單獨控制某個數據庫備份的時間;
b) 數據庫一多的話,在Job的Message里面沒有辦法顯示那么多的信息;
c) 備份串行執行的,沒有辦法進行並行備份;
d) 個個數據庫的備份不是單獨的,如果作業出錯,會造成后面的數據庫無法備份;
e) 貌似是先寫入內存,等作業完成后才一次性寫入硬盤的?當數據庫備份文件比較大的時候會莫名只備份幾個數據庫作業就退出了,沒有查明是什么原因;
具體的操作步驟可以參考:SQL Server 批量主分區備份(One Job),這個方案最終的作業形式為:
(Figure2:作業列表)
方案三是為了控制備份的時間而准備的,就是每個數據庫都創建一個作業,這樣的好處是可以充分的控制到每個數據庫,因為我們可以需要對某個數據庫進行完整備份,而且備份的粒度也有可能不同(重要客戶的備份粒度會更小)
(Figure3:作業列表)
既然我們選擇了方案三,那我們如何快速(批量)創建這些作業呢?我們先以數據庫Barefoot.Opinion.9197為例,創建出一個作業的模板后,通過修改、替換這個作業的代碼來實現批量生成新的可執行的作業腳本;
通過下面的幾個步驟你就可以批量的生成備份作業:
1) 備份主分區完整備份的SQL代碼(備份配置表);備份主分區差異備份的SQL代碼(減輕備份文件空間壓力)
2) 自動刪除備份文件的SQL代碼;(保證磁盤有足夠空間)
3) 批量創建文件夾;(每個數據庫單獨一個文件夾)
4) 批量創建作業方案;(每個作業的名稱、目錄都不相同)
5) 批量修改作業計划的時間;(均衡分配作業的執行時間)
6) 批量刪除作業;(方便維護)
7) 查看作業的執行情況;(防止作業時間過長,過長可以考慮預警)
四.實現代碼(SQL Codes)
(一) 下面的代碼實現了主分區完整備份和主分區差異備份,當是星期一的深夜的時候,我們做完整備份,如果是其它時候我們就做差異備份,具體是什么時候,這個就通過計划里面的時候來控制了(計划的執行時間為星期一、星期三、星期五,這就代表星期一深夜做了完整備份、星期三和星期五分別做了差異備份)。
生成的備份文件名為:(這樣的文件名方便閱讀;而且能精確到秒,重復的幾率不大)
DBName _Primary_Full_2013_01_14_002007.bak
DBName_Primary_Diff_2013_01_16_002034.bak
--1設置完整模式 USE [master] GO ALTER DATABASE [DBName.9197] SET RECOVERY FULL WITH NO_WAIT GO --2備份主分區 DECLARE @CurrentTime VARCHAR(50), @FileName VARCHAR(200) SET @CurrentTime = REPLACE(REPLACE(REPLACE(CONVERT(VARCHAR, GETDATE(), 120 ),'-','_'),' ','_'),':','') IF(DATEPART(DW, GETDATE()) = 2)--星期一 BEGIN --完整備份 SET @FileName = 'E:\DBBackup\DBName.9197\DBName.9197_Primary_Full_' + @CurrentTime+'.bak' BACKUP DATABASE [DBName.9197] FILEGROUP='PRIMARY' TO DISK=@FileName WITH FORMAT END ELSE BEGIN --差異備份 SET @FileName = 'E:\DBBackup\DBName.9197\DBName.9197_Primary_Diff_' + @CurrentTime+'.bak' BACKUP DATABASE [DBName.9197] FILEGROUP='PRIMARY' TO DISK=@FileName WITH DIFFERENTIAL,FORMAT END GO --3設置簡單模式 USE [master] GO ALTER DATABASE [DBName.9197] SET RECOVERY SIMPLE WITH NO_WAIT GO
(二) 下面的代碼實現了刪除備份文件,從下面的代碼中可以看出刪除了14天之前的備份文件;
--刪除之前的備份文件 DECLARE @DeleteDate DATETIME SET @DeleteDate = DATEADD(DAY, -14, GETDATE()) EXECUTE MASTER.SYS.XP_DELETE_FILE 0, N'E:\DBBackup\DBName.9197\', N'bak', @DeleteDate
(三) 下面的代碼實現了批量創建文件夾,需要開啟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
(四) 下面的代碼實現了批量創建作業,這里有一個創建作業的模板:JobTemplet.sql,我寫了一個Replaced.bat的批處理文件,這個批處理文件替換模板文件中數據庫的字符串,並生成創建新數據庫作業的SQL腳本,創建之后就會執行這個SQL腳本來創建作業;
1. 使用上面提供的SQL代碼,創建好作業的步驟和計划,再使用SSMS生成創建作業的腳本,這個就是JobTemplet.sql;
USE [msdb] GO /****** 對象: Job [Barefoot_Opinion_9565] 腳本日期: 01/06/2013 14:07:27 ******/ BEGIN TRANSACTION DECLARE @ReturnCode INT SELECT @ReturnCode = 0 /****** 對象: JobCategory [Database Maintenance] 腳本日期: 01/06/2013 14:07:27 ******/ IF NOT EXISTS (SELECT name FROM msdb.dbo.syscategories WHERE name=N'Database Maintenance' AND category_class=1) BEGIN EXEC @ReturnCode = msdb.dbo.sp_add_category @class=N'JOB', @type=N'LOCAL', @name=N'Database Maintenance' IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback END DECLARE @jobId BINARY(16) EXEC @ReturnCode = msdb.dbo.sp_add_job @job_name=N'DBName_9565', @enabled=1, @notify_level_eventlog=0, @notify_level_email=0, @notify_level_netsend=0, @notify_level_page=0, @delete_level=0, @description=N'備份主分區', @category_name=N'Database Maintenance', @owner_login_name=N'oofraBnimdA_gz', @job_id = @jobId OUTPUT IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback /****** 對象: Step [Bakcup] 腳本日期: 01/06/2013 14:07:27 ******/ EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @job_id=@jobId, @step_name=N'Bakcup', @step_id=1, @cmdexec_success_code=0, @on_success_action=3, @on_success_step_id=0, @on_fail_action=2, @on_fail_step_id=0, @retry_attempts=0, @retry_interval=0, @os_run_priority=0, @subsystem=N'TSQL', @command=N'--1設置完整模式 USE [master] GO ALTER DATABASE [DBName.9565] SET RECOVERY FULL WITH NO_WAIT GO --2備份主分區(完整備份) DECLARE @CurrentTime VARCHAR(50), @FileName VARCHAR(200) SET @CurrentTime = REPLACE(REPLACE(REPLACE(CONVERT(VARCHAR, GETDATE(), 120 ),''-'',''_''),'' '',''_''),'':'','''') IF(DATEPART(DW, GETDATE()) = 2)--星期一 BEGIN SET @FileName = ''E:\DBBackup\DBName.9565\DBName.9565_Primary_Full_'' + @CurrentTime+''.bak'' BACKUP DATABASE [DBName.9565] FILEGROUP=''PRIMARY'' TO DISK=@FileName WITH FORMAT END ELSE BEGIN SET @FileName = ''E:\DBBackup\DBName.9565\DBName.9565_Primary_Diff_'' + @CurrentTime+''.bak'' BACKUP DATABASE [DBName.9565] FILEGROUP=''PRIMARY'' TO DISK=@FileName WITH DIFFERENTIAL,FORMAT END GO --3設置簡單模式 USE [master] GO ALTER DATABASE [DBName.9565] SET RECOVERY SIMPLE WITH NO_WAIT GO ', @database_name=N'DBName.9565', @flags=0 IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback /****** 對象: Step [Delete] 腳本日期: 01/06/2013 14:07:27 ******/ EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @job_id=@jobId, @step_name=N'Delete', @step_id=2, @cmdexec_success_code=0, @on_success_action=1, @on_success_step_id=0, @on_fail_action=2, @on_fail_step_id=0, @retry_attempts=0, @retry_interval=0, @os_run_priority=0, @subsystem=N'TSQL', @command=N'--刪除之前的備份文件 DECLARE @DeleteDate DATETIME SET @DeleteDate = DATEADD(DAY, -14, GETDATE()) EXECUTE MASTER.SYS.XP_DELETE_FILE 0, N''E:\DBBackup\DBName.9565\'', N''bak'', @DeleteDate', @database_name=N'DBName.9565', @flags=0 IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback EXEC @ReturnCode = msdb.dbo.sp_update_job @job_id = @jobId, @start_step_id = 1 IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback EXEC @ReturnCode = msdb.dbo.sp_add_jobschedule @job_id=@jobId, @name=N'Plan', @enabled=1, @freq_type=8, @freq_interval=34, @freq_subday_type=1, @freq_subday_interval=0, @freq_relative_interval=0, @freq_recurrence_factor=1, @active_start_date=20130105, @active_end_date=99991231, @active_start_time=10000, @active_end_time=235959 IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback EXEC @ReturnCode = msdb.dbo.sp_add_jobserver @job_id = @jobId, @server_name = N'(local)' IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback COMMIT TRANSACTION GOTO EndSave QuitWithRollback: IF (@@TRANCOUNT > 0) ROLLBACK TRANSACTION EndSave:
2. 把下面的代碼保存為Replaced.bat文件,這個批處理文件接受兩個參數,一個是@OldStr一個是@NewStr,即舊的數據庫名和新的數據庫名;
@if "%1"=="" goto error_parm @if "%2"=="" goto error_parm @echo off setlocal enabledelayedexpansion set file=E:\DBBackup\JobTemplet.sql set "file=%file:"=%" for %%i in ("%file%") do set file=%%~fi echo. set replaced=%1 echo. set all=%2 for /f "delims=" %%i in ('type "%file%"') do ( set str=%%i set "str=!str:%replaced%=%all%!" echo !str!>>"%file%"_%2.sql )
3. 到這里我們已經有JobTemplet.sql和Replaced.bat文件了,我們再創建一個Replaced_Test.bat批處理進行測試,這個批處理設置兩個變量,一個是@OldStr一個是@NewStr,再調用Replaced.bat來替換字符串;
@set temp1=9565
@set temp2=9001
call Replaced_2.bat %temp1%,%temp2%
通過上面3個步驟,創建了3個文件,如下圖所示:
(Figure4:創建的模板和批處理文件)
4. 下面的SQL代碼就是使用游標的形式,循環符合條件的數據庫,使用批處理替換模板文件,生成新的T-SQL文件,再執行新生成的T-SQL文件,這樣就創建了游標當前數據庫的備份作業了:
-- ============================================= -- Author: <聽風吹雨> -- Blog: <http://gaizai.cnblogs.com/> -- Create date: <2013/12/03> -- Description: <批量創建作業的T-SQL文件(替換字符串),並執行這個T-SQL文件> -- ============================================= --開啟高級功能 EXEC sp_configure 'show advanced options', 1 RECONFIGURE EXEC sp_configure 'xp_cmdshell', 1 RECONFIGURE DECLARE @DBName VARCHAR(100)--數據庫名稱 DECLARE @CmdFile VARCHAR(1000)--創建File的Cmd命令字符串 DECLARE @CmdJob VARCHAR(1000)--執行Job的Cmd命令字符串 DECLARE @OldStr varchar(100)--需要替換的字符串 DECLARE @NewStr varchar(100)--替換成字符串 SET @OldStr = '9565' --查找符合條件的數據庫 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 --使用批處理替換模板文件,生成新的T-SQL文件 SET @NewStr = SUBSTRING(@DBName,LEN('DBName.')+1,LEN(@DBName)) SET @CmdFile = 'E:\DBBackup\Replaced.bat ' + @OldStr + ' ' + @NewStr PRINT @CmdFile EXEC xp_cmdshell @CmdFile --執行新生成的T-SQL文件 SET @CmdJob = 'sqlcmd -i"E:\DBBackup\JobTemplet.sql _'+@NewStr+'.sql"' PRINT @CmdJob EXEC xp_cmdshell @CmdJob --循環下一個數據庫 FETCH NEXT FROM CurDBName INTO @DBName END CLOSE CurDBName DEALLOCATE CurDBName --關閉高級功能 EXEC sp_configure 'xp_cmdshell', 0 RECONFIGURE EXEC sp_configure 'show advanced options', 0 RECONFIGURE
上面的腳本中你需要注意兩點,如下圖所示:“bat”字符串后面是有一個空格的,如果沒有這個空格就會報錯的;另外一點就是“sql”字符串與“_”之間也是有個空格的,這個空格是因為在使用Replaced.bat批處理創建文件的文件名的時候多一個空格(暫時還沒找出原因),所以這里的文件名稱需要加一個空格;
執行完上面的腳本之后,在E:\DBBackup的目錄下會生成下圖所示的SQL腳本文件:
(五) 到這里,SSMS中已經創建了10個作業,就如下圖所顯示:
(Figure7:創建的作業列表)
但是為了能分散作業的執行時間,我們有兩種方式做到這點:
1) 在JobTemplet.sql中有一個參數@active_start_time=10000,這個參數的意思是在深夜1點鍾的時候執行作業,所以可以在Replaced.bat這個批處理再加一個參數,並且在JobTemplet.sql中替換成我們想要的值,在外層T-SQL調用Replaced.bat的時候記錄這個值,並傳入到Replaced.bat中(這就留給讀者自己實現吧,我只實現了下面的第二種方法);
2) 修改已經創建作業的執行時間,下面的代碼實現了批量修改作業的執行時間,使用游標的形式,循環調用msdb.dbo.sp_update_schedule修改作業的執行時間;
USE [msdb] GO -- ============================================= -- Author: <聽風吹雨> -- Blog: <http://gaizai.cnblogs.com/> -- Create date: <2013/12/03> -- Description: <批量修改Job備份時間> -- ============================================= DECLARE @DBName VARCHAR(100) DECLARE @ScheduleId INT DECLARE @Date DATETIME DECLARE @Time VARCHAR(50) DECLARE @SQL VARCHAR(1000) SET @Date = '2013-01-08 00:20:00.000' DECLARE CurDBName CURSOR FOR SELECT name,schedule_id FROM SYSJOBS AS J LEFT JOIN [SYSJOBSCHEDULES] AS S ON J.job_id= S.job_id WHERE NAME LIKE 'DBName_%' ORDER BY NAME OPEN CurDBName FETCH NEXT FROM CurDBName INTO @DBName,@ScheduleId WHILE @@FETCH_STATUS = 0 BEGIN --修改作業的執行時間 SET @Time = REPLACE(CONVERT(VARCHAR, @Date, 8 ),':','') SET @SQL = 'EXEC msdb.dbo.sp_update_schedule @schedule_id = ''' + CONVERT(VARCHAR(50),@ScheduleId) + ''', @active_start_time=' + @Time PRINT(@DBName +':'+ @SQL) EXEC(@SQL) --遞增分鍾 SET @Date = DATEADD(mi,20,@Date) --Get Next DataBase FETCH NEXT FROM CurDBName INTO @DBName,@ScheduleId END CLOSE CurDBName DEALLOCATE CurDBName
(六) 下面的代碼實現了批量刪除作業,使用游標的形式,循環調用sp_delete_job;
USE [msdb] GO -- ============================================= -- Author: <聽風吹雨> -- Blog: <http://gaizai.cnblogs.com/> -- Create date: <2013/12/03> -- Description: <批量刪除Job> -- ============================================= DECLARE @DBName VARCHAR(100) DECLARE CurDBName CURSOR FOR SELECT name FROM msdb.dbo.sysjobs OPEN CurDBName FETCH NEXT FROM CurDBName INTO @DBName WHILE @@FETCH_STATUS = 0 BEGIN --刪除Job exec sp_delete_job @job_name = @DBName --Get Next DataBase FETCH NEXT FROM CurDBName INTO @DBName END CLOSE CurDBName DEALLOCATE CurDBName
(七) 查看作業的運行情況;
--查詢作業的執行情況 SELECT b.name,b.enabled,b.description,b.date_created,b.date_modified, a.step_id,a.step_name,message,run_date,run_time,run_duration FROM [msdb].dbo.[sysjobhistory] AS a LEFT JOIN [msdb].[dbo].[sysjobs] AS b ON a.job_id = b.job_id ORDER BY name
(Figure8:作業執行情況)
--查詢作業與計划的對應關系 SELECT J.name,schedule_id FROM [msdb].[dbo].[sysjobs] AS J LEFT JOIN [msdb].[dbo].[sysjobschedules] AS S on J.job_id= S.job_id WHERE J.name LIKE '%' ORDER BY J.name
(Figure9:作業與計划的對應關系)
在表[msdb].[dbo].[sysschedules]中也同樣包含作業計划信息;
(Figure10:sysschedules信息)
五.主分區完整、差異還原(Primary Backup And Restore)
既然做了上面主文件組的備份,當然我們需要去測試這個主文件組的還原了,這樣才可以當遇到問題可以快速還原備份文件,達到還原數據的目的;
接下來會在另外一篇文章里面專門講解;
六.參考文獻(References)
sp_update_schedule (Transact-SQL)