Transfer semua database MS SQL Server ke komputer lain

Baru-baru ini, ada kebutuhan untuk mentransfer semua database (> 50 pada satu contoh SQL Server) dari lingkungan dev ke contoh lain dari SQL Server, yang terletak pada perangkat keras yang berbeda. Saya ingin meminimalkan kerja manual dan melakukan segalanya secepat mungkin.

Penolakan


Skrip ditulis untuk satu situasi tertentu: ini adalah lingkungan dev, semua database dalam model pemulihan sederhana, file data dan log transaksi berada di tumpukan yang sama.

Segala sesuatu yang ditulis di bawah ini hanya berlaku untuk situasi ini, tetapi Anda dapat dengan mudah menyelesaikannya sendiri (kondisi Anda) tanpa banyak usaha.

Skrip tidak menggunakan STRING_AGG baru-ketinggalan jaman dan hal-hal baik lainnya, jadi semuanya harus bekerja dimulai dengan SQL Server 2008 (atau 2008 R2, saya tidak ingat di mana kompresi cadangan muncul). Untuk versi yang lebih lama, Anda perlu menghapus DENGAN KOMPRESI dari perintah cadangan, tetapi mungkin tidak ada lagi perbedaan waktu dengan menyalin file.

Ini bukan instruksi - "bagaimana" melakukan transfer semacam itu. Ini adalah demonstrasi bagaimana metadata dapat digunakan dalam SQL dinamis.

Tentu saja, cara tercepat adalah dengan menyambungkan kembali rak disk ke server baru, tapi itu bukan pilihan kami. Melepaskan - menyalin - Melampirkan dianggap, tetapi tidak cocok, karena salurannya cukup sempit dan mentransfer database dalam bentuk yang tidak terkompresi akan memakan waktu yang agak lama.

Akibatnya, kami memutuskan bahwa kami akan melakukan pencadangan dengan kompresi pada bola di server baru, dan kemudian mengembalikannya di sana. Setrika di lokasi lama dan baru tidak buruk, cadangannya tidak buruk, perolehan waktu juga tidak buruk.

Jadi "pembuat skrip" ditulis:

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';

Di bagian keluaran, kami mendapatkan perintah siap pakai untuk membuat cadangan ke tempat yang tepat, mentransfer basis data secara offline, sehingga pengguna mereka tidak dapat bekerja dengannya di server lama dan skrip untuk memulihkan cadangan yang diterima di server baru (dengan transfer otomatis semua file data dan log transaksi ke lokasi yang ditentukan )

Masalah dengan ini adalah bahwa seseorang harus duduk dan menjalankan semua skrip pada gilirannya (backup-offline pemulihan), atau seseorang harus terlebih dahulu memulai semua cadangan, kemudian lepaskan semua database, lalu kembalikan semuanya - ada lebih sedikit tindakan, tetapi Anda harus duduk dan lacak.

Saya ingin mengotomatiskan semua operasi ini. Di satu sisi, semuanya sederhana - sudah ada perintah yang sudah jadi, bungkus kursor dan jalankan. Dan, pada prinsipnya, saya melakukan hal itu, menambahkan server baru sebagai server yang terhubung pada yang lama, dan memulainya. Di server lokal, perintah dijalankan melalui EXECUTE (@sql_text); pada server yang terhubung, EXECUTE (@sql_text) AT [linkedServerName].

Dengan demikian, operasi dilakukan secara berurutan - cadangan secara lokal, menerjemahkan database lokal secara offline, memulihkan ke server Linked. Semuanya dimulai, bersorak-sorai, tetapi bagi saya tampaknya Anda dapat mempercepat prosesnya sedikit jika cadangan dan pemulihan dilakukan secara independen satu sama lain.
Kemudian kursor yang ditemukan dibagi menjadi dua bagian - pada server lama dalam kursor, setiap basis data dicadangkan dan ditransfer secara offline, setelah itu server kedua harus memahami bahwa tugas baru telah muncul dan melakukan pemulihan basis data. Untuk menerapkan mekanisme ini, saya menggunakan catatan dalam tabel di server yang ditautkan dan loop tak terbatas (saya terlalu malas untuk datang dengan kriteria berhenti), yang terlihat untuk melihat apakah ada catatan baru dan sedang mencoba mengembalikan sesuatu, jika ada.

Keputusan


Di server lama, tabel sementara global ## CommandList dibuat dan diisi, di mana semua perintah dikumpulkan dan dimungkinkan untuk melacak status cadangan di tempat yang sama. Tabelnya bersifat global sehingga setiap saat dari sesi lain Anda dapat melihat apa yang terjadi di sana sekarang.

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"

Mari kita lihat apa yang terjadi di sana (SELECT * FROM ## CommandList):



Hebat, semua perintah dikumpulkan di sana untuk membuat cadangan / mengembalikan semua database yang diperlukan.

Database Pemeliharaan dibuat di server baru dan tabel CommandList ada di dalamnya, yang akan berisi informasi tentang memulihkan database:

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)				-- ,  
);

Server yang ditautkan telah dikonfigurasi pada server lama, dengan melihat contoh baru dari SQL Server. Skrip yang diberikan dalam posting ini, saya tulis di rumah dan tidak repot dengan contoh baru, saya menggunakan satu dan menghubungkannya sebagai server yang terhubung ke diri saya sendiri. Oleh karena itu, di sini saya memiliki jalur yang sama dan unc-path lokal.

Sekarang Anda dapat mendeklarasikan kursor untuk mencadangkan basis data, memutuskan sambungannya dan menulis perintah untuk memulihkan ke server yang ditautkan:

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;

Setiap tindakan "dicatat" pada tab Pesan di SSMS - di sana Anda dapat mengamati tindakan saat ini. Jika Anda menggunakan WITH LOG di RAISERROR, pada prinsipnya, Anda bisa memasukkan semuanya ke dalam beberapa pekerjaan dan kemudian melihat log.

Saat runtime, Anda dapat mengakses ## CommandList dan melihat tabel apa yang terjadi dan bagaimana.

Di server baru, secara paralel, putaran tanpa akhir berputar:


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

Segala sesuatu yang dia lakukan - melihat ke dalam tabel CommandList, jika ada setidaknya satu catatan mentah di sana - mengambil nama database dan perintah untuk memulihkan dan mencoba mengeksekusinya menggunakan EXEC (@sql_text); Jika tidak ada entri, tunggu 30 detik dan coba lagi.

Baik kursor dan loop memproses setiap record hanya satu kali. Tidak berhasil? Kami menulis pesan kesalahan ke tabel dan tidak kembali ke sini lagi.

Tentang kondisi untuk berhenti - saya sebenarnya malas. Saat mengetik, saya menemukan paling tidak tiga solusi - sebagai opsi - menambahkan tanda “Ready for recovery \ Not ready for recovery \ Finished”, mengisi daftar database dan perintah dengan segera, ketika mengisi ## CommandList pada server lama dan memperbarui bendera di dalam kursor. Kami berhenti ketika tidak ada "catatan siap untuk pemulihan" yang tersisa, karena kami segera tahu jumlah pekerjaan penuh.

temuan


Namun tidak ada kesimpulan. Saya pikir mungkin bermanfaat / menarik bagi seseorang untuk melihat bagaimana menggunakan metadata untuk membentuk dan menjalankan sql dinamis. Skrip yang diberikan dalam posting, karena itu, tidak cocok untuk digunakan pada prod, namun, mereka dapat sedikit selesai untuk diri mereka sendiri dan digunakan, misalnya, untuk kustomisasi massal pengiriman log / basis data mirroring / ketersediaan kelompok.

Saat melakukan pencadangan pada bola, akun yang menjalankan SQL Server harus memiliki izin untuk menulis di sana.

Pembuatan Linked Server tidak diungkapkan dalam pos (mouse di GUI secara intuitif dikonfigurasi dalam beberapa menit) dan transfer login ke server baru. Mereka yang telah mengalami migrasi pengguna tahu bahwa hanya menciptakan kembali login sql tidak banyak membantu, karena mereka memiliki sisi yang terhubung dengan pengguna database. Skrip untuk menghasilkan login sql dengan kata sandi saat ini dan sid yang benar ada di msdn .

All Articles