(05/04/2014) Procedure Upgrades :
– Added compresson to reduce bandwith,space necessary and transfer time
– Reduced stat value for very large database
– Added backup type parameter to choose from FULL,FULL_COPYONLY or DIFFERENTIAL backup (for bigger database migration)
– Added Maxtransfersize and Buffercount parameters to improve backup performance (warning – this needs more memory!)
– Corrected a bug to show correct error message of any kind
Introduction :
This is my complete procedure to accomplish this task, completely based on TSQL and metascripts. Nothing more is necessary.
There are many ways to migrate databases (backup-restore, detach-copy-attach, sql database copy tool and so on..). In this procedure we’ll follow the backup-restore way. Why? Simply because is more flexible and require less bandwith and disk space then others. Obviusly every DBA have to choose the correct strategy for every scenario but this way is in my opinon one of the most adaptable for complex instances with many and big databases to transfer.
Those are the macro-steps we’ll follow to reach our goal:
- Migrate instance logins keeping the original SID and password .
- Backup databases and transfer to the destination sites
- Restore database relocating datafiles in the new fs
Migration of logins with original sid and password hash
This step is necessary to transfer logins from the source instance. We want to keep the source user password and the source user sid. The source user sid is necessary to preserve alignment between instance login SIDs and database user SIDs. The correct procedure to complete this task change for different SQL Server version because in different builds are used different ways to store login informations in system catalog.
The complete MS procedures are here:
http://support.microsoft.com/kb/246133/en-us (SQL 2000)
http://support.microsoft.com/kb/918992/en-us (SQL 2005,2008,2012*) *see note at page bottom
Here you can see the complete script for a common scenario (2008):
1) Run this script in the source instance and save the complete output as text (ALT+T before execute in SSMS).
USE [master] GO /****** Object: UserDefinedFunction [dbo].[fn_hexadecimal] ****/ SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO CREATE FUNCTION [dbo].[fn_hexadecimal] ( -- Add the parameters for the function here @binvalue varbinary(256) ) RETURNS VARCHAR(256) AS BEGIN DECLARE @charvalue varchar(256) DECLARE @i int DECLARE @length int DECLARE @hexstring char(16) SELECT @charvalue = '0x' SELECT @i = 1 SELECT @length = DATALENGTH (@binvalue) SELECT @hexstring = '0123456789ABCDEF' WHILE (@i <= @length) BEGIN DECLARE @tempint int DECLARE @firstint int DECLARE @secondint int SELECT @tempint = CONVERT(int, SUBSTRING(@binvalue,@i,1)) SELECT @firstint = FLOOR(@tempint/16) SELECT @secondint = @tempint - (@firstint*16) SELECT @charvalue = @charvalue + SUBSTRING(@hexstring, @firstint+1, 1) + SUBSTRING(@hexstring, @secondint+1, 1) SELECT @i = @i + 1 END return @charvalue END GO SET NOCOUNT ON GO --use MASTER GO PRINT '-----------------------------------------------------------------------------' PRINT '-- Script created on ' + CAST(GETDATE() AS VARCHAR(100)) PRINT '-----------------------------------------------------------------------------' PRINT '' PRINT '-----------------------------------------------------------------------------' PRINT '-- Create the windows logins' PRINT '-----------------------------------------------------------------------------' SELECT 'IF NOT EXISTS (SELECT * FROM master.sys.server_principals WHERE [name] = ''' + [name] + ''') CREATE LOGIN [' + [name] + '] FROM WINDOWS WITH DEFAULT_DATABASE=[' + default_database_name + '], DEFAULT_LANGUAGE=[us_english] GO ' FROM master.sys.server_principals where type_desc In ('WINDOWS_GROUP', 'WINDOWS_LOGIN') AND [name] not like 'BUILTIN%' and [NAME] not like 'NT AUTHORITY%' and [name] not like '%SQLServer%' GO PRINT '-----------------------------------------------------------------------------' PRINT '-- Create the SQL Logins' PRINT '-----------------------------------------------------------------------------' select 'IF NOT EXISTS (SELECT * FROM master.sys.sql_logins WHERE [name] = ''' + [name] + ''') CREATE LOGIN [' + [name] + '] WITH PASSWORD=' + [master].[dbo].[fn_hexadecimal](password_hash) + ' HASHED, SID = ' + [master].[dbo].[fn_hexadecimal]([sid]) + ', DEFAULT_DATABASE=[' + default_database_name + '], DEFAULT_LANGUAGE=[us_english], CHECK_EXPIRATION=' + CASE WHEN is_expiration_checked = 1 THEN 'ON' ELSE 'OFF' END + ', CHECK_POLICY=OFF GO IF EXISTS (SELECT * FROM master.sys.sql_logins WHERE [name] = ''' + [name] + ''') ALTER LOGIN [' + [name] + '] WITH CHECK_EXPIRATION=' + CASE WHEN is_expiration_checked = 1 THEN 'ON' ELSE 'OFF' END + ', CHECK_POLICY=' + CASE WHEN is_policy_checked = 1 THEN 'ON' ELSE 'OFF' END + ' GO ' --[name], [sid] , password_hash from master.sys.sql_logins where type_desc = 'SQL_LOGIN' and [name] not in ('sa', 'guest') PRINT '-----------------------------------------------------------------------------' PRINT '-- Disable any logins' PRINT '-----------------------------------------------------------------------------' SELECT '--ALTER LOGIN [' + [name] + '] DISABLE GO ' from master.sys.server_principals where is_disabled = 1 PRINT '-----------------------------------------------------------------------------' PRINT '-- Assign groups' PRINT '-----------------------------------------------------------------------------' select 'EXEC master..sp_addsrvrolemember @loginame = N''' + l.name + ''', @rolename = N''' + r.name + ''' GO ' from master.sys.server_role_members rm join master.sys.server_principals r on r.principal_id = rm.role_principal_id join master.sys.server_principals l on l.principal_id = rm.member_principal_id where l.[name] not in ('sa') AND l.[name] not like 'BUILTIN%' and l.[NAME] not like 'NT AUTHORITY%' and l.[name] not like '%SQLServer%'
2) Run the result script in the destination instance
Backup databases and transfer to the destination site
This step is not diffuclt but can be very long and frustrating on big instances with many databases. This is a metascript to generate and run the backup script in a smarter way skipping and logging errors with no halts.
---------------------------------------------------- --CONFIGURE HERE ---------------------------------------------------- -- DESTINATION PATH ---------------------------------------------------- declare @destpath nvarchar(255) = 'G:backup' ---------------------------------------------------- -- BACKUP TYPE (use FULL_COPYONLY | FULL | DIFF) ---------------------------------------------------- declare @bck_type nvarchar(25) = 'FULL_COPYONLY' ---------------------------------------------------- -- DATABASE LIST ---------------------------------------------------- create table #dbs (dbname nvarchar(255)) insert into #dbs values('db1') insert into #dbs values('db2') insert into #dbs values('db3') --Note: if you want to move ALL database simply insert all db names in #dbs table using: -- Insert into #dbs select name from master..sysdatabases --TO backup every user db excluding system database use -- Insert into #dbs select name from master..sysdatabases where -- name not in ('master','model','msdb','tempdb') ---------------------------------------------------- declare @aDB nvarchar(255) declare @aQry nvarchar(2048) DECLARE em_cur CURSOR FOR select * from #dbs OPEN em_cur FETCH NEXT FROM em_cur INTO @aDB WHILE @@FETCH_STATUS = 0 BEGIN set @aQry = 'BACKUP DATABASE [' + @aDB + '] TO DISK = ''' + @destpath + @aDB + '.bak'' WITH NOFORMAT, NOINIT, NAME = N''' + @aDB + '-Full Database Backup'', SKIP, NOREWIND, NOUNLOAD, STATS = 3, COMPRESSION' --ADD buffercount = 64, maxtransfersize = 4194304 to improve performance. --WARNING!! This require MORE memory and can lead to OUT OF MEMORY errors. Use and raise this parameters ONLY --when you are sure this will not create any problem to your users (with stopped applications or with less used instances) if (@bck_type='DIFF') BEGIN set @aQry = @aQry + ',DIFFERENTIAL' END ELSE BEGIN if (@bck_type='FULL_COPYONLY') BEGIN set @aQry = @aQry + ',COPY_ONLY' END END begin try print '-------------' print @aQry exec (@aQry) print '-------------' end try begin catch print 'ERROR during backup: ' + ERROR_MESSAGE() end catch FETCH NEXT FROM em_cur INTO @aDB END CLOSE em_cur DEALLOCATE em_cur drop table #dbs
Restore database relocating datafiles in the new fs
On the source instance use this metascript to generate the restore script. We’ll use the generated script to restore all databases (into the destination instance) relocating datafiles in the new fs.
set nocount on ---------------------------------------------------- --CONFIGURE HERE ---------------------------------------------------- -- DESTINATION PATH ---------------------------------------------------- --backup files position declare @restorepath nvarchar(max) = 'G:backup' --data files destination declare @pathdata nvarchar(max) = 'D:DataPath' --log files destination declare @pathlog nvarchar(max) = 'L:LogPath' ---------------------------------------------------- -- DATABASE LIST ---------------------------------------------------- create table #dbs (dbname nvarchar(255)) insert into #dbs values('db1') insert into #dbs values('db2') insert into #dbs values('db3') --Note: if you want to move ALL database simply insert all db names in #dbs table using: --Insert into #dbs select name from master..sysdatabases --------------------------------------------------- declare @aDB nvarchar(255) declare em_cur CURSOR FOR select * from #dbs OPEN em_cur FETCH NEXT FROM em_cur INTO @aDB WHILE @@FETCH_STATUS = 0 BEGIN select 'RESTORE DATABASE [' + @aDB + '] from disk=N''' + @restorepath + @aDB + '.bak'' WITH ' union all select CASE WHEN m.type_desc = 'ROWS' THEN 'MOVE N''' + m.name + ''' TO N''' + @pathdata + RIGHT(physical_name, CHARINDEX('', REVERSE(physical_name))-1) +''',' WHEN m.type_desc = 'LOG' THEN 'MOVE N''' + m.name + ''' TO N''' + @pathlog + RIGHT(physical_name, CHARINDEX('', REVERSE(physical_name))-1) +''',' END from sys.master_files m inner join sys.databases d on (m.database_id = d.database_id) where d.name=@aDB union all select 'NOUNLOAD, STATS = 10' print 'GO' FETCH NEXT FROM em_cur INTO @aDB END CLOSE em_cur DEALLOCATE em_cur drop table #dbs
Now take the generated script and run it on the destination instance to restore every database in the correct position.
*NOTE: (Update 22-07-2012) In my current tests on SQL 2012 the official MS procedure to export login password hashes export procedure does not function. To export logins with original password hash AND the original SID use the following workaround
select convert(varchar(50),'CREATE LOGIN [' + name + '] with password='),CAST(password AS VARBINARY(26)), 'HASHED,SID=', CAST(sid AS VARBINARY(26)) from sys.syslogins where password is not null
Great info, thx.
I have a sql server 2008 250G db in simple recovery mode. How would advise moving it to another server across country with the least amount of down time?
"Mi piace""Mi piace"
In simple recovery mode you can use Differential Backup. So:
– Backup and Restore (with no-recovery) a full backup to your destination server.
– In a low-transactional time window stop applicatoins
– Make differential backup to take last transactions
– Restore to destination server the diff backup (with recovery).
– Change applications database settings to target the new server
– Open Application services to your customers
And remamber to:
– make some test to check how long backup full and diff take
– stop index reorganize rebuilds before tests. Index maintenance makes diff backup bigger and longer
– set your original database offline to be sure all applications are pointing the moved one
"Mi piace""Mi piace"
Why do you disable [sa]?
"Mi piace""Mi piace"
Here is what the script produced
—————————————————————————–
— Disable any logins
—————————————————————————–
————————————————————————————————————————————————————
ALTER LOGIN [sa] DISABLE
GO
"Mi piace""Mi piace"
Please, run script using a sysadmin user. Users with no administrative rights can’t run query on sys database schema.
"Mi piace""Mi piace"
This metascript creates script to disable users in the destination database with no filters. The administrator can choose to use them or not before running the resulting script.
"Mi piace""Mi piace"