Übertragen Sie alle MS SQL Server-Datenbanken auf einen anderen Computer

Vor kurzem mussten alle Datenbanken (> 50 auf einer Instanz von SQL Server) von der Entwicklungsumgebung auf eine andere Instanz von SQL Server übertragen werden, die sich auf einer anderen Hardware befand. Ich wollte die Handarbeit minimieren und alles so schnell wie möglich erledigen.

Haftungsausschluss


Skripte werden für eine bestimmte Situation geschrieben: Dies ist eine Entwicklungsumgebung. Alle Datenbanken in einem einfachen Wiederherstellungsmodell, Datendateien und Transaktionsprotokolle befinden sich auf demselben Heap.

Alles, was unten geschrieben steht, gilt nur für diese Situation, aber Sie können sie ohne großen Aufwand leicht für sich selbst (Ihre Bedingungen) erledigen.

Die Skripte verwenden nicht das neue STRING_AGG und andere nette Dinge, daher sollte ab SQL Server 2008 alles funktionieren (oder 2008 R2, ich kann mich nicht erinnern, wo die Sicherungskomprimierung aufgetreten ist). Bei älteren Versionen müssen Sie WITH COMPRESSION aus dem Sicherungsbefehl entfernen, aber dann gibt es möglicherweise keine Zeitunterschiede mehr beim Kopieren von Dateien.

Dies ist keine Anweisung - wie man eine solche Übertragung durchführt. Dies ist eine Demonstration, wie Metadaten in dynamischem SQL verwendet werden können.

Der schnellste Weg wäre natürlich, das Festplattenfach einfach wieder mit dem neuen Server zu verbinden, aber das war nicht unsere Option. Trennen - Kopieren - Anhängen wurde in Betracht gezogen, passte aber nicht, da der Kanal ziemlich eng war und das Übertragen der Datenbank in unkomprimierter Form ziemlich lange dauern würde.

Aus diesem Grund haben wir beschlossen, ein Backup mit Komprimierung auf dem Ball auf dem neuen Server zu erstellen und es dort wiederherzustellen. Das Bügeleisen sowohl am alten als auch am neuen Standort ist nicht schlecht, das Backup ist nicht schlecht, der Zeitgewinn ist auch nicht schlecht.

Also wurde der "Skriptgenerator" geschrieben:

DECLARE @unc_backup_path AS varchar(max) = '\\newServer\backup_share\'
	, @local_backup_path AS varchar(max) = 'E:\Backup\'
	, @new_data_path as varchar(max) = 'D:\SQLServer\data\';

SELECT name	
	, 'BACKUP DATABASE [' + name + '] TO DISK = ''' + @unc_backup_path + name + '.bak'' WITH INIT, COPY_ONLY, STATS = 5;' AS backup_command
	, 'ALTER DATABASE [' + name + '] SET OFFLINE WITH ROLLBACK IMMEDIATE;' AS offline_command
	, 'RESTORE DATABASE [' + name + '] FROM DISK = ''' + @local_backup_path + name + '.bak'' WITH ' 
		+ (
			SELECT 'MOVE ''' + mf.name + ''' TO ''' + 
				@new_data_path + REVERSE(LEFT(REVERSE(mf.physical_name), CHARINDEX('\', REVERSE(mf.physical_name))-1)) +
				''', '
			FROM sys.master_files mf
			WHERE mf.database_id = d.database_id
			FOR XML PATH('')
		) + 'REPLACE, RECOVERY, STATS = 5;' AS restore_command
FROM sys.databases d
WHERE database_id > 4 AND state_desc = N'ONLINE';

Bei der Ausgabe erhalten wir vorgefertigte Befehle zum Erstellen von Sicherungen an den richtigen Ort und zum Offline-Übertragen der Datenbank, sodass ihre Benutzer nicht mit ihnen auf dem alten Server und Skripten arbeiten können, um die empfangenen Sicherungen auf dem neuen Server wiederherzustellen (mit automatischer Übertragung aller Datendateien und Transaktionsprotokolle an den angegebenen Speicherort )

Das Problem dabei ist, dass entweder jemand sitzen und alle Skripte nacheinander ausführen muss (Backup-Offline-Wiederherstellung), oder jemand muss zuerst alle Sicherungen starten, dann alle Datenbanken trennen und dann alles wiederherstellen - es gibt weniger Aktionen, aber Sie müssen sitzen und verfolgen.

Ich wollte all diese Vorgänge automatisieren. Einerseits ist alles einfach - es gibt bereits vorgefertigte Befehle, den Cursor einschließen und ausführen. Und im Prinzip habe ich genau das getan, einen neuen Server als Verbindungsserver zum alten hinzugefügt und ihn gestartet. Auf dem lokalen Server wurde der Befehl über EXECUTE (@sql_text) ausgeführt, auf dem Verbindungsserver EXECUTE (@sql_text) AT [linkedServerName].

Daher wurden Vorgänge nacheinander ausgeführt - lokale Sicherung, Offline-Übersetzung der lokalen Datenbank und Wiederherstellung auf dem Verbindungsserver. Alles begann, Prost, aber es schien mir, dass Sie den Prozess ein wenig beschleunigen können, wenn Backups und Restaurationen unabhängig voneinander durchgeführt werden.
Dann wurde der erfundene Cursor in zwei Teile geteilt - auf dem alten Server im Cursor wird jede Datenbank gesichert und offline übertragen. Danach muss der zweite Server verstehen, dass eine neue Aufgabe aufgetreten ist, und die Datenbank wiederherstellen. Um diesen Mechanismus zu implementieren, habe ich einen Datensatz in einer Tabelle auf einem Verbindungsserver und eine Endlosschleife verwendet (ich war zu faul, um Stoppkriterien zu finden), um festzustellen, ob neue Datensätze vorhanden sind, und um zu versuchen, gegebenenfalls etwas wiederherzustellen.

Entscheidung


Auf dem alten Server wird eine globale temporäre Tabelle ## CommandList erstellt und ausgefüllt, in der alle Befehle gesammelt werden und der Status von Sicherungen an derselben Stelle verfolgt werden kann. Die Tabelle ist global, sodass Sie jederzeit von einer anderen Sitzung aus sehen können, was dort gerade passiert.

DECLARE @unc_backup_path AS varchar(max) = 'D:\SQLServer\backup\' --       
	, @local_backup_path AS varchar(max) = 'D:\SQLServer\backup\'	--        
	, @new_data_path as varchar(max) = 'D:\SQLServer\data\';		--      ,    

SET NOCOUNT ON;

IF OBJECT_ID ('tempdb..##CommandList', 'U') IS NULL
	CREATE TABLE ##CommandList (
		dbName sysname unique			-- 
		, backup_command varchar(max)	--   
		, offline_command varchar(max)	--        
		, restore_command varchar(max)	--       
		, processed bit				-- : NULL -  , 0 -  , 1 - 
		, start_dt datetime			--  
		, finish_dt datetime			--  
		, error_msg varchar(max)		--  ,  
	);

INSERT INTO ##CommandList (dbname, backup_command, offline_command, restore_command)
SELECT name	
	, 'BACKUP DATABASE [' + name + '] TO DISK = ''' + @unc_backup_path + name + '.bak'' WITH INIT, COPY_ONLY, STATS = 5;' AS backup_command -- INIT -      
	, 'ALTER DATABASE [' + name + '] SET OFFLINE WITH ROLLBACK IMMEDIATE;' AS offline_command
	, 'RESTORE DATABASE [' + name + '] FROM DISK = ''' + @local_backup_path + name + '.bak'' WITH ' 
		+ (
			SELECT 'MOVE ''' + mf.name + ''' TO ''' + 
				@new_data_path + REVERSE(LEFT(REVERSE(mf.physical_name), CHARINDEX('\', REVERSE(mf.physical_name))-1)) +
				''', '	
			FROM sys.master_files mf
			WHERE mf.database_id = d.database_id
			FOR XML PATH('')
		) + 'REPLACE, RECOVERY, STATS = 5;' AS restore_command	
FROM sys.databases d
WHERE database_id > 4 
	AND state_desc = N'ONLINE'
	AND name NOT IN (SELECT dbname FROM ##CommandList)
	AND name <> 'Maintenance';	--  linked server -    ,   ,    "linked server"

Mal sehen, was dort passiert ist (SELECT * FROM ## CommandList):



Großartig, alle Befehle werden dort gesammelt, um alle erforderlichen Datenbanken zu sichern / wiederherzustellen.

Die Wartungsdatenbank wurde auf dem neuen Server erstellt und enthält die CommandList-Tabelle, die Informationen zum Wiederherstellen der Datenbanken enthält:

USE [Maintenance]
GO

CREATE TABLE CommandList (
	dbName sysname unique				-- 
	, restore_command varchar(max)		--  
	, processed bit						-- 
	, creation_dt datetime DEFAULT GETDATE()	--  
	, start_dt datetime					--  
	, finish_dt datetime					--  
	, error_msg varchar(max)				-- ,  
);

Auf dem alten Server wurde ein Verbindungsserver konfiguriert, der sich die neue Instanz von SQL Server ansieht. Die Skripte, die in diesem Beitrag enthalten sind, habe ich zu Hause geschrieben und mich nicht um eine neue Instanz gekümmert. Ich habe eine verwendet und sie als Verbindungsserver mit mir selbst verbunden. Daher habe ich hier die gleichen Pfade und Unpfad lokal.

Jetzt können Sie einen Cursor deklarieren, in dem die Datenbanken gesichert, getrennt und ein Befehl zum Wiederherstellen auf dem Verbindungsserver geschrieben werden sollen:

DECLARE @dbname AS sysname
	, @backup_cmd AS varchar(max)
	, @restore_cmd AS varchar(max)
	, @offline_cmd AS varchar(max);

DECLARE MoveDatabase CURSOR
FOR 
SELECT dbName, backup_command, offline_command, restore_command
FROM ##CommandList
WHERE processed IS NULL;

OPEN MoveDatabase;

FETCH NEXT FROM MoveDatabase INTO @dbname, @backup_cmd, @offline_cmd, @restore_cmd;

WHILE @@FETCH_STATUS = 0
	BEGIN
		--    ,  :
		--  
		--   -      
		--    ,      
		--     

		--    
		UPDATE ##CommandList
		SET start_dt = GETDATE()
		WHERE dbName = @dbname;

		BEGIN TRY
			
			RAISERROR ('  %s', 0, 1, @dbname) WITH NOWAIT; --   messages   
			
			--  
			EXEC (@backup_cmd);

			RAISERROR ('    %s', 0, 1, @dbname) WITH NOWAIT;

			--    -  linked server
			INSERT INTO [(LOCAL)].[Maintenance].[dbo].[CommandList] (dbName, restore_command)
			VALUES (@dbname, @restore_cmd);

			RAISERROR (' %s  OFFLINE', 0, 1, @dbname) WITH NOWAIT;

			--    
			EXEC (@offline_cmd);

			--  ,    
			UPDATE ##CommandList
			SET processed = 0
				, finish_dt = GETDATE()
			WHERE dbName = @dbname;

		END TRY
		BEGIN CATCH
			
			RAISERROR ('    %s.   error_msg  ##CommandList', 0, 1, @dbname) WITH NOWAIT;

			--  -   ,      
			UPDATE ##CommandList
			SET processed = 1
				, finish_dt = GETDATE()
				, error_msg = ERROR_MESSAGE();

		END CATCH

		FETCH NEXT FROM MoveDatabase INTO @dbname, @backup_cmd, @offline_cmd, @restore_cmd;
	END

CLOSE MoveDatabase;

DEALLOCATE MoveDatabase;

-- 
SELECT dbName
	, CASE processed WHEN 1 THEN '' WHEN 0 THEN '' ELSE ' ' END as Status 
	, start_dt
	, finish_dt
	, error_msg
FROM ##CommandList
ORDER BY start_dt;

DROP TABLE ##CommandList;

Jede Aktion wird auf der Registerkarte Nachrichten in SSMS „protokolliert“ - dort können Sie die aktuelle Aktion beobachten. Wenn Sie WITH LOG in RAISERROR verwenden, können Sie im Prinzip alles in einen Job einfügen und dann die Protokolle anzeigen.

Zur Laufzeit können Sie auf die ## CommandList zugreifen und in tabellarischer Form sehen, was wie passiert.

Parallel dazu drehte sich auf dem neuen Server eine Endlosschleife:


SET NOCOUNT ON;

DECLARE @dbname AS sysname
	, @restore_cmd AS varchar(max);

WHILE 1 = 1	--   ,    
BEGIN
	SELECT TOP 1 @dbname = dbName, @restore_cmd = restore_command 
	FROM CommandList
	WHERE processed IS NULL; --    ,  

	IF @dbname IS NOT NULL 
	BEGIN
		--    
		UPDATE CommandList
		SET start_dt = GETDATE()
		WHERE dbName = @dbname;

		RAISERROR('  %s', 0, 1, @dbname) WITH NOWAIT;
		
		BEGIN TRY

			--  ,  -  ,  CATCH    
			EXEC (@restore_cmd);

			--   
			UPDATE CommandList
			SET processed = 0
				, finish_dt = GETDATE()
			WHERE dbName = @dbname;

			RAISERROR(' %s  ', 0, 1, @dbname) WITH NOWAIT;

		END TRY
		BEGIN CATCH

			RAISERROR('    %s', 0, 1, @dbname) WITH NOWAIT;

			UPDATE CommandList 
			SET processed = 1
				, finish_dt = GETDATE()
				, error_msg = ERROR_MESSAGE();

		END CATCH

	END
	ELSE	--   ,    
		BEGIN

			RAISERROR('waiting', 0, 1) WITH NOWAIT;

			WAITFOR DELAY '00:00:30';

		END
		
	SET @dbname = NULL;
	SET @restore_cmd = NULL;

END

Alles, was er tut - schaut in die CommandList-Tabelle, wenn dort mindestens ein Rohdatensatz vorhanden ist - nimmt den Datenbanknamen und den Befehl zum Wiederherstellen und versucht, ihn mit EXEC (@sql_text) auszuführen; Wenn keine Einträge vorhanden sind, warten Sie 30 Sekunden und versuchen Sie es erneut.

Sowohl der Cursor als auch die Schleife verarbeiten jeden Datensatz nur einmal. Hat nicht funktioniert? Wir schreiben eine Fehlermeldung in die Tabelle und kehren hier nicht mehr zurück.

Über die Bedingung zum Anhalten - ich war eigentlich faul. Während der Eingabe habe ich mindestens drei Lösungen gefunden - als Option - die Flags "Bereit zur Wiederherstellung \ Nicht bereit für die Wiederherstellung \ Fertig" hinzugefügt, die Liste der Datenbanken und Befehle sofort ausgefüllt, wenn ## CommandList auf dem alten Server ausgefüllt und das Flag im Cursor aktualisiert wurde. Wir hören auf, wenn keine „Datensätze zur Wiederherstellung bereit“ mehr vorhanden sind, da wir sofort den vollen Arbeitsaufwand kennen.

Ergebnisse


Es gibt jedoch keine Schlussfolgerungen. Ich dachte, es könnte für jemanden nützlich / interessant sein, zu sehen, wie Metadaten zum Erstellen und Ausführen von dynamischem SQL verwendet werden. Die in der Veröffentlichung angegebenen Skripte sind nicht für die Verwendung auf dem Produkt geeignet. Sie können jedoch für sich selbst leicht fertiggestellt und beispielsweise für die Massenanpassung von Protokollversand- / Datenbankspiegelungs- / Verfügbarkeitsgruppen verwendet werden.

Wenn Sie eine Sicherung für den Ball durchführen, muss das Konto, unter dem SQL Server ausgeführt wird, über Schreibberechtigungen verfügen.

Die Erstellung des Verbindungsservers wurde im Beitrag nicht bekannt gegeben (die Maus in der GUI wird in wenigen Minuten intuitiv konfiguriert) und die Übertragung von Anmeldungen auf den neuen Server. Diejenigen, die eine Benutzermigration erlebt haben, wissen, dass das einfache Neuerstellen von SQL-Anmeldungen nicht viel hilft, da sie Seiten haben, mit denen Datenbankbenutzer verbunden sind. Skripte zum Generieren von SQL-Anmeldungen mit aktuellen Kennwörtern und korrekter Sid befinden sich auf msdn .

All Articles