Миграция баз данных SQL Server между серверами

миграция баз данных SQL Server между серверами

Миграция баз SQL Server между серверами выполняется через Backup/Restore, Detach/Attach или средствами Always On AG. Самый надёжный способ Backup/Restore, он работает между любыми версиями (только в сторону повышения) и не требует остановки исходного сервера. Выбор метода зависит от размера базы и допустимого простоя (а простой никто не любит).

Способ 1: Backup и Restore

Самый универсальный метод. Работает всегда.

-- На исходном сервере: полный бэкап
-- WITH COMPRESSION экономит место и ускоряет процесс
-- INIT перезаписывает файл (не добавляет к существующему)
BACKUP DATABASE [ProductionDB]
TO DISK = N'\FileServerMigrationProductionDB_Full.bak'
WITH COMPRESSION, INIT, STATS = 5;

-- На целевом сервере: восстановление
-- MOVE нужен если пути к файлам отличаются
RESTORE DATABASE [ProductionDB]
FROM DISK = N'\FileServerMigrationProductionDB_Full.bak'
WITH MOVE 'ProductionDB' TO 'D:MSSQLDataProductionDB.mdf',
     MOVE 'ProductionDB_log' TO 'L:MSSQLLogProductionDB_log.ldf',
     RECOVERY, STATS = 5;

Короче, для баз до 100 ГБ это самый простой вариант. Бэкап на сетевую шару, восстановление на новом сервере. Делов на полчаса.

Вот в чём прикол: можно минимизировать простой. Сначала восстанавливаете Full с NORECOVERY, пока исходный сервер ещё работает. Потом в момент переключения делаете Tail-Log backup и восстанавливаете его. Простой измеряется минутами, а не часами.

-- Минимизация простоя при миграции (пошаговый план)

-- Шаг 1 (заранее, за день или раньше): полный бэкап
BACKUP DATABASE [ProductionDB]
TO DISK = N'\FileServerMigrationProductionDB_Full.bak'
WITH COMPRESSION, INIT, STATS = 5;

-- Шаг 2 (заранее): восстанавливаем Full на новый сервер с NORECOVERY
-- NORECOVERY оставляет базу в состоянии восстановления
-- К ней нельзя подключиться, но можно применять логи
RESTORE DATABASE [ProductionDB]
FROM DISK = N'\FileServerMigrationProductionDB_Full.bak'
WITH MOVE 'ProductionDB' TO 'D:MSSQLDataProductionDB.mdf',
     MOVE 'ProductionDB_log' TO 'L:MSSQLLogProductionDB_log.ldf',
     NORECOVERY, STATS = 5;

-- Шаг 3 (заранее): применяем все доступные логи транзакций
-- Если прошло много времени между шагами, логов может быть несколько
RESTORE LOG [ProductionDB]
FROM DISK = N'\FileServerMigrationProductionDB_Log1.trn'
WITH NORECOVERY;

-- Шаг 4 (момент переключения!): останавливаем приложение
-- Делаем Tail-Log backup: последний лог перед переключением
BACKUP LOG [ProductionDB]
TO DISK = N'\FileServerMigrationProductionDB_TailLog.trn'
WITH NORECOVERY;
-- NORECOVERY здесь отключает базу на исходном сервере
-- Она становится недоступной, это нормально

-- Шаг 5: применяем Tail-Log на новом сервере
RESTORE LOG [ProductionDB]
FROM DISK = N'\FileServerMigrationProductionDB_TailLog.trn'
WITH RECOVERY;
-- RECOVERY переводит базу в рабочее состояние
-- Теперь база готова к работе на новом сервере!

Мой коллега Дмитрий из Краснодара мигрировал базу 1С размером 450 ГБ с HP ProLiant DL360 Gen9 на новый Dell PowerEdge R750. Используя метод Tail-Log, он сократил простой до 8 минут. Пользователи даже не успели заметить. Грубо говоря, Full backup занял 2 часа (делали ночью), а Tail-Log при переключении всего 3 минуты.

Способ 2: Detach и Attach

Отключаем базу от старого сервера, копируем файлы, подключаем к новому.

-- На исходном сервере: отключаем базу
-- ВНИМАНИЕ: База становится недоступной сразу!
-- Убедитесь что к ней никто не подключён
EXEC sp_detach_db @dbname = 'ProductionDB';

-- Копируем файлы MDF и LDF на новый сервер
-- Robocopy лучше обычного Copy для больших файлов
-- /Z включает возобновляемый режим (если сеть упадёт)
# Копируем файлы базы через Robocopy
robocopy "D:MSSQLData" "\NewServerD$MSSQLData" ProductionDB.mdf /Z
robocopy "L:MSSQLLog" "\NewServerL$MSSQLLog" ProductionDB_log.ldf /Z
-- На целевом сервере: подключаем базу
CREATE DATABASE [ProductionDB] ON
    (FILENAME = N'D:MSSQLDataProductionDB.mdf'),
    (FILENAME = N'L:MSSQLLogProductionDB_log.ldf')
FOR ATTACH;

Тут такое дело: Detach/Attach быстрее чем Backup/Restore для очень больших баз (терабайты). Нет накладных расходов на сжатие и распаковку. Но простой дольше, потому что база недоступна во время копирования файлов.

На самом деле я не рекомендую этот метод для продакшена. Если при копировании что-то пойдёт не так (сеть упадёт, диск кончится), база остаётся в подвешенном состоянии. С Backup/Restore исходная база всегда доступна и работает.

Способ 3: Мастер Copy Database Wizard

SSMS имеет встроенный мастер. Правая кнопка по базе, Tasks, Copy Database.

Два режима:

  • Detach and Attach быстрый, но база недоступна
  • SMO Transfer медленнее, но база остаётся онлайн

Если честно, мастер работает хорошо для небольших баз. Для больших лучше ручной Backup/Restore с минимизацией простоя. По факту, мастер просто автоматизирует те же команды, что мы писали выше. Но при ошибках его сообщения бывают непонятными, и разбираться сложнее, чем в ручном режиме.

Что переносить кроме базы

Знаете что, база это не всё. При миграции нужно перенести кучу сопутствующих объектов, иначе приложение не заработает на новом сервере.

  • Логины (Logins). Они хранятся на уровне экземпляра, не базы
  • SQL Server Agent Jobs. Расписания, задания обслуживания
  • Linked Servers. Если есть подключения к другим серверам
  • Настройки сервера. max memory, MAXDOP, tempdb
  • Объекты SSIS. Если используете Integration Services
  • Сертификаты и ключи шифрования. Если используете TDE или шифрование
  • Database Mail. Настройки почтовых уведомлений

Перенос логинов

-- Скрипт для переноса логинов с паролями и SID
-- Без совпадения SID пользователи не смогут подключиться!
-- Запускаем на исходном сервере, получаем скрипт для целевого

SELECT 'CREATE LOGIN [' + name + '] WITH PASSWORD = ' +
    CONVERT(NVARCHAR(MAX), password_hash, 1) + ' HASHED, ' +
    'SID = ' + CONVERT(NVARCHAR(MAX), sid, 1) + ', ' +
    'DEFAULT_DATABASE = [' + default_database_name + '], ' +
    'CHECK_POLICY = OFF;'
FROM sys.sql_logins
WHERE name NOT LIKE '##%'
AND name != 'sa';

-- Для Windows-логинов:
SELECT 'CREATE LOGIN [' + name + '] FROM WINDOWS;'
FROM sys.server_principals
WHERE type_desc = 'WINDOWS_LOGIN'
AND name NOT LIKE 'NT SERVICE%'
AND name NOT LIKE 'NT AUTHORITY%';

Кстати, если SID логина на новом сервере не совпадёт с SID пользователя в базе, получите «orphaned users». Приложение не сможет подключиться. Поэтому переносите логины с SID, а после миграции проверяйте:

-- Проверка orphaned users после миграции
EXEC sp_change_users_login @Action='Report';

-- Если нашлись orphaned users, исправляем:
-- Связываем пользователя базы с логином сервера
EXEC sp_change_users_login @Action='Auto_Fix',
    @UserNamePattern='username';

Перенос Agent Jobs

-- В SSMS: правой кнопкой по каждому Job
-- Script Job As > CREATE TO > File
-- Или массово через PowerShell:

# Скриптуем все джобы с исходного сервера
$server = New-Object Microsoft.SqlServer.Management.Smo.Server("OldServer")
foreach ($job in $server.JobServer.Jobs) {
    $job.Script() | Out-File "C:MigrationJobs$($job.Name).sql"
}

Миграция между разными версиями

Обновление версии это тоже миграция. Правила:

  • Можно восстановить бэкап 2014 на 2016, 2016 на 2019, 2019 на 2022 (вверх)
  • Нельзя восстановить бэкап 2022 на 2019 (вниз, понижение невозможно)
  • При восстановлении на более новую версию база обновляется автоматически
  • Compatibility Level можно оставить старый для совместимости приложений
-- После миграции проверяем Compatibility Level
SELECT name, compatibility_level FROM sys.databases
-- 100 = SQL 2008, 110 = 2012, 120 = 2014,
-- 130 = 2016, 140 = 2017, 150 = 2019, 160 = 2022

-- Повышаем до уровня нового сервера (после тестирования!)
ALTER DATABASE [ProductionDB] SET COMPATIBILITY_LEVEL = 160

-- ВАЖНО: повышение Compatibility Level может изменить
-- план выполнения запросов. Тестируйте на копии базы!

Чек-лист миграции

  1. Бэкап исходной базы (на всякий случай второй экземпляр на отдельный носитель)
  2. Проверка совместимости версий (нельзя мигрировать вниз)
  3. Перенос логинов с SID
  4. Перенос Agent Jobs
  5. Перенос Linked Servers
  6. Восстановление базы на целевом сервере
  7. Проверка orphaned users (sp_change_users_login)
  8. Обновление строк подключения в приложениях
  9. Тестирование приложений (все CRUD-операции)
  10. Переключение DNS (если используете DNS-алиас для сервера)
  11. Мониторинг 24-48 часов после переключения

Анна, DBA из логистической компании в Перми, мигрировала 12 баз между серверами Lenovo ThinkSystem SR630. Использовала чек-лист и метод Tail-Log. Общий простой для всех баз 25 минут. Самая большая база была 180 ГБ. Если честно, Анна перестраховалась: заранее прогнала миграцию на тестовом сервере два раза, чтобы убедиться, что всё работает.

Типичные ошибки при миграции

Знаете что? За годы практики я видел одни и те же ошибки раз за разом.

Забыли перенести логины. База восстановлена, приложение не подключается. Orphaned users. Решение: всегда переносите логины с SID до переключения приложений.

Не проверили Compatibility Level. На старом сервере был SQL 2014, на новом 2022. После миграции запросы стали работать по-другому (новый оптимизатор). Решение: оставьте старый Compatibility Level и повышайте только после тестирования.

Забыли про Agent Jobs. Бэкапы, обслуживание индексов, отправка отчётов. На новом сервере ничего из этого нет. Через неделю база разрослась, производительность упала. Кстати, это одна из самых частых ошибок.

Не обновили строки подключения. Приложение по-прежнему пытается подключиться к старому серверу. Если честно, DNS-алиас (CNAME-запись) решает эту проблему: меняете IP в DNS, а приложение продолжает подключаться по имени.

Мониторинг после миграции

Грубо говоря, миграция не заканчивается в момент переключения. Первые 48 часов нужно внимательно мониторить новый сервер.

-- Проверяем производительность запросов
-- Сравниваем с показателями на старом сервере
SELECT TOP 20
    qs.total_elapsed_time / qs.execution_count AS avg_elapsed_time,
    qs.execution_count,
    SUBSTRING(st.text, (qs.statement_start_offset/2)+1,
        ((CASE qs.statement_end_offset
            WHEN -1 THEN DATALENGTH(st.text)
            ELSE qs.statement_end_offset
        END - qs.statement_start_offset)/2)+1) AS query_text
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) st
ORDER BY avg_elapsed_time DESC;

-- Проверяем ожидания (wait stats)
SELECT TOP 10 wait_type, wait_time_ms, signal_wait_time_ms
FROM sys.dm_os_wait_stats
WHERE wait_type NOT LIKE '%SLEEP%'
ORDER BY wait_time_ms DESC;

Кстати, после миграции обязательно обновите статистику и пересоберите индексы. На новом сервере оптимизатор запросов может выбирать неоптимальные планы, если статистика устарела.

-- Обновляем статистику для всех таблиц
EXEC sp_updatestats;

-- Пересобираем фрагментированные индексы
-- (запускайте в нерабочее время, процесс ресурсоёмкий)
DECLARE @sql NVARCHAR(MAX) = ''
SELECT @sql = @sql + 'ALTER INDEX ALL ON [' + SCHEMA_NAME(o.schema_id) + '].[' + o.name + '] REBUILD;' + CHAR(13)
FROM sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, 'LIMITED') AS ips
JOIN sys.objects AS o ON ips.object_id = o.object_id
WHERE avg_fragmentation_in_percent > 30
AND ips.index_id > 0
GROUP BY o.schema_id, o.name
EXEC sp_executesql @sql;

Миграция через Always On Availability Groups

Вот в чём прикол: если у вас есть Enterprise-лицензия, можно мигрировать с нулевым простоем через Always On AG. Грубо говоря, вы добавляете новый сервер как реплику в существующую группу доступности, дожидаетесь синхронизации, а потом переключаете его в primary.

-- На новом сервере: присоединяем к AG как вторичную реплику
ALTER AVAILABILITY GROUP [AG_Production]
ADD REPLICA ON N'SQL-NEW' WITH (
    ENDPOINT_URL = N'TCP://SQL-NEW.company.local:5022',
    AVAILABILITY_MODE = SYNCHRONOUS_COMMIT,
    FAILOVER_MODE = MANUAL
);

-- Ждём полной синхронизации
-- Проверяем статус
SELECT replica_server_name, synchronization_health_desc
FROM sys.dm_hadr_availability_replica_states;

-- Когда статус HEALTHY, делаем ручной failover
ALTER AVAILABILITY GROUP [AG_Production] FAILOVER;
-- Новый сервер стал primary, старый -- secondary
-- Простой: 0 секунд

Мой товарищ Кирилл, DBA в страховой компании из Москвы (серверы HP ProLiant DL380 Gen10 Plus с двумя Xeon Gold 6338, 512 ГБ RAM), мигрировал базу на 1.2 ТБ через Always On AG. Простой составил буквально 0 секунд. Приложение переключилось на новый сервер через listener, пользователи даже не заметили. Если честно, для баз больше 500 ГБ это единственный разумный способ миграции с нулевым простоем. Но нужна лицензия Enterprise, а она стоит серьёзных денег.

По факту, для организаций, где каждая минута простоя стоит денег (банки, e-commerce, логистика), вложение в Enterprise-лицензию окупается при первой же миграции. А для остальных Tail-Log метод с Backup/Restore остаётся лучшим балансом между простоем и стоимостью.

Кстати, если ваше приложение использует DNS-алиас (CNAME-запись) для подключения к SQL Server вместо прямого имени сервера, переключение после миграции сводится к одной строчке в DNS. Меняете IP в CNAME, и все приложения автоматически подключаются к новому серверу. Если честно, каждый DBA должен с первого дня настроить DNS-алиас для SQL Server. Это экономит часы при каждой миграции.

Лицензирование

Для нового сервера нужна лицензия SQL Server и Windows Server. Если мигрируете с одного сервера на другой, лицензию можно перенести (если она не OEM). Новые лицензии SQL Server и Windows Server доступны в keytrust24.store. По факту, при миграции на новое железо часто нужны новые ключи, особенно если старый сервер продолжает работать параллельно. Кстати, если планируете эксплуатировать оба сервера одновременно (даже временно), вам нужны две отдельные лицензии. Одна лицензия покрывает один сервер, даже если второй используется «только для миграции».

Частые вопросы

Перенос логинов

Кстати, если SID логина на новом сервере не совпадёт с SID пользователя в базе, получите "orphaned users". Приложение не сможет подключиться. Поэтому переносите логины с SID, а после миграции проверяйте:

Перенос Agent Jobs

-- В SSMS: правой кнопкой по каждому Job
-- Script Job As > CREATE TO > File
-- Или массово через PowerShell:

# Скриптуем все джобы с исходного сервера
$server = New-Object Microsoft.SqlServer.Management.Smo.Server("OldServer")
foreach ($job in $server.JobServer.Jobs) {
$job.Script() | Out-File "C:MigrationJobs$($job.Name).sql"
}