نقل كافة قواعد بيانات MS SQL Server إلى جهاز آخر

في الآونة الأخيرة ، كانت هناك حاجة إلى نقل جميع قواعد البيانات (> 50 في مثيل واحد من SQL Server) من بيئة التطوير إلى مثيل آخر من SQL Server ، والذي كان موجودًا على جهاز مختلف. كنت أرغب في تقليل العمل اليدوي والقيام بكل شيء في أسرع وقت ممكن.

تنصل


تتم كتابة البرامج النصية لموقف واحد محدد: هذه بيئة مطورة ، وجميع قواعد البيانات في نموذج استرداد بسيط ، وملفات البيانات وسجلات المعاملات في نفس الكومة.

كل شيء مكتوب أدناه ينطبق فقط على هذا الموقف ، ولكن يمكنك إنهاءها بسهولة بنفسك (شروطك) دون بذل الكثير من الجهد.

لا تستخدم النصوص البرمجية STRING_AGG الجديد المتشابك وأشياء لطيفة أخرى ، لذلك يجب أن يعمل كل شيء بدءًا من SQL Server 2008 (أو 2008 R2 ، لا أتذكر مكان ظهور ضغط النسخ الاحتياطي). بالنسبة للإصدارات الأقدم ، تحتاج إلى إزالة WITH COMPRESSION من أمر النسخ الاحتياطي ، ولكن بعد ذلك قد لا يكون هناك اختلافات في الوقت مع نسخ الملفات.

هذه ليست تعليمات - "كيفية" القيام بهذا التحويل. هذا عرض توضيحي لكيفية استخدام البيانات الوصفية في SQL الديناميكية.

بالطبع ، أسرع طريقة هي ببساطة إعادة ربط رف القرص بالخادم الجديد ، لكن هذا لم يكن خيارنا. فصل - نسخ - تم النظر في إرفاق ، ولكن لم يكن مناسبًا ، نظرًا لأن القناة كانت ضيقة جدًا وسيستغرق نقل قاعدة البيانات في شكل غير مضغوط فترة زمنية طويلة إلى حد ما.

ونتيجة لذلك ، قررنا أن نقوم بعمل نسخة احتياطية مع الضغط على الكرة على الخادم الجديد ، ومن ثم استعادتها هناك. الحديد في الموقع القديم والجديد ليس سيئًا ، والنسخ الاحتياطي ليس سيئًا ، ومكسب الوقت ليس سيئًا أيضًا.

لذلك تمت كتابة "مولد البرنامج النصي":

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

عند الإخراج ، نحصل على أوامر جاهزة لإنشاء نسخ احتياطية إلى المكان الصحيح ، وترجمة قاعدة البيانات في وضع عدم الاتصال ، بحيث لا يستطيع المستخدمون العمل معهم على الخادم القديم والنصوص البرمجية لاستعادة النسخ الاحتياطية المستلمة على الخادم الجديد (مع النقل التلقائي لجميع ملفات البيانات وسجلات المعاملات إلى الموقع المحدد )

المشكلة في ذلك هي أنه إما أن يضطر شخص ما إلى الجلوس وتنفيذ جميع البرامج النصية بدوره (النسخ الاحتياطي دون اتصال) ، أو يجب على شخص ما بدء جميع النسخ الاحتياطية ، ثم فصل جميع قواعد البيانات ، ثم استعادة كل شيء - هناك إجراءات أقل ، ولكن عليك الجلوس وتتبع.

أردت أتمتة كل هذه العمليات. من ناحية ، كل شيء بسيط - هناك بالفعل أوامر جاهزة ، التفاف في المؤشر وتنفيذها. ومن حيث المبدأ ، لقد فعلت ذلك ، أضفت خادمًا جديدًا كخادم مرتبط على الخادم القديم ، وبدأت في ذلك. على الخادم المحلي ، تم تنفيذ الأمر من خلال EXECUTE (sql_text) ؛ على الخادم المرتبط ، EXECUTE (sql_text) AT [linksServerName].

وهكذا ، تم تنفيذ العمليات بالتتابع - النسخ الاحتياطي محليًا ، وترجمة قاعدة البيانات المحلية دون اتصال ، واستعادتها إلى الخادم المرتبط. كل شيء بدأ ، هتاف ، ولكن بدا لي أنه يمكنك تسريع العملية قليلاً إذا تم إجراء عمليات النسخ الاحتياطي والترميم بشكل مستقل عن بعضها البعض.
ثم تم تقسيم المؤشر المخترع إلى جزأين - على الخادم القديم في المؤشر ، يتم نسخ كل قاعدة بيانات احتياطيًا ونقلها في وضع عدم الاتصال ، وبعد ذلك يجب أن يفهم الخادم الثاني ظهور مهمة جديدة واستعادة قاعدة البيانات. لتطبيق هذه الآلية ، استخدمت سجلًا في جدول على خادم مرتبط وحلقة لا نهائية (كنت كسولًا جدًا لدرجة أنني لم أتمكن من وضع معايير التوقف) ، والتي تبدو لمعرفة ما إذا كانت هناك سجلات جديدة وتحاول استعادة شيء ما ، إن وجد.

القرار


على الخادم القديم ، يتم إنشاء جدول مؤقت عالمي ## CommandList وملئه ، حيث يتم جمع جميع الأوامر وسيكون من الممكن تتبع حالة النسخ الاحتياطية في نفس المكان. الجدول عالمي بحيث يمكنك في أي لحظة من جلسة أخرى أن ترى ما يحدث هناك الآن.

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"

دعنا نرى ما حدث هناك (SELECT * FROM ## CommandList):



رائع ، يتم جمع جميع الأوامر هناك للنسخ الاحتياطي / استعادة جميع قواعد البيانات الضرورية.

تم إنشاء قاعدة بيانات الصيانة على الخادم الجديد وجدول CommandList فيها ، والذي سيحتوي على معلومات حول استعادة قواعد البيانات:

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

تم تكوين خادم مرتبط على الخادم القديم ، بالنظر إلى المثيل الجديد لـ SQL Server. النصوص البرمجية المعطاة في هذا المنشور ، كتبت في المنزل ولم أزعج مثيل جديد ، استخدمت واحدًا وربطته كخادم مرتبط بنفسي. لذلك ، لدي هنا نفس المسارات وغير المسار المحلي.

يمكنك الآن الإعلان عن مؤشر يتم فيه نسخ قواعد البيانات احتياطيًا وفصلها وكتابة أمر لاستعادته إلى الخادم المرتبط:

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;

يتم "تسجيل" كل إجراء في علامة التبويب "الرسائل" في SSMS - حيث يمكنك مراقبة الإجراء الحالي. إذا كنت تستخدم WITH LOG في RAISERROR ، من حيث المبدأ ، يمكنك وضع كل شيء في بعض الوظائف ثم النظر في السجلات.

في وقت التشغيل ، يمكنك الوصول إلى ## CommandList والاطلاع في شكل جدولي على ما يحدث وكيف.

على الخادم الجديد ، بالتوازي ، تدور حلقة لا نهائية:


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

كل ما يفعله - ينظر إلى جدول CommandList ، إذا كان هناك سجل خام واحد على الأقل - يأخذ اسم قاعدة البيانات والأمر لاستعادته ويحاول تنفيذه باستخدام EXEC (sql_text) ؛. إذا لم تكن هناك إدخالات ، انتظر 30 ثانية وحاول مرة أخرى.

كل من المؤشر والحلقة يعالج كل سجل مرة واحدة فقط. لم ينجح في مبتغاه؟ نكتب رسالة خطأ إلى الجدول ولا نعود هنا بعد الآن.

حول حالة التوقف - كنت كسولًا في الواقع. أثناء الكتابة ، توصلت إلى ثلاثة حلول على الأقل - كخيار - بإضافة إشارات "جاهزة للاستعادة \ غير جاهزة للاستعادة \ انتهى" ، وملء قائمة قواعد البيانات والأوامر فورًا ، عند ملء ## CommandList على الخادم القديم وتحديث العلامة داخل المؤشر. نتوقف عند عدم وجود "سجلات جاهزة للاسترداد" ، لأننا نعرف على الفور مقدار العمل الكامل.

الموجودات


لكن لا توجد استنتاجات. اعتقدت أنه قد يكون من المفيد / المثير للاهتمام أن يرى شخص ما كيفية استخدام البيانات الوصفية لتكوين وتنفيذ sql ديناميكي. البرامج النصية الواردة في المنشور ، كما هي ، ليست مناسبة للاستخدام في المنتج ، ومع ذلك ، يمكنك إضافتها قليلاً لنفسك واستخدامها ، على سبيل المثال ، لتكوين مجموعات الشحن / النسخ المطابق لقاعدة البيانات / الإتاحة.

عند إجراء نسخة احتياطية على الكرة ، يجب أن يكون للحساب الذي يعمل ضمنه SQL Server أذونات للكتابة هناك.

لم يتم الكشف عن إنشاء Linked Server في المشاركة (تم تكوين الماوس في واجهة المستخدم الرسومية بشكل حدسي في بضع دقائق) ونقل عمليات تسجيل الدخول إلى الخادم الجديد. يعلم أولئك الذين عانوا من ترحيل المستخدم أن إعادة إنشاء تسجيلات الدخول إلى SQL لا تساعد كثيرًا ، لأن لديهم جانبًا يرتبط به مستخدمو قاعدة البيانات. توجد برامج نصية لإنشاء تسجيلات دخول SQL مع كلمات المرور الحالية و sid الصحيح على msdn .

All Articles