Introducción
Hay una serie de situaciones que justificarían el movimiento de archivos de base de datos o archivos de registro de transacciones de un volumen a otro en el mismo servidor. Estos pueden incluir:
- La necesidad de formatear el volumen suponiendo que no se formateó correctamente cuando se instaló SQL Server . Recuerde que al instalar SQL Server, se recomienda utilizar un tamaño de unidad de asignación de 64 K para formatear los volúmenes. Si esto no se hace en el momento de la instalación y debe hacerse más tarde, obviamente será necesario conservar una copia de seguridad de la base de datos o crear un volumen nuevo con el formato adecuado y mover la base de datos a este nuevo volumen.
- La necesidad de usar un nuevo volumen suponiendo que se hayan alcanzado los límites para el almacenamiento subyacente . Un buen ejemplo sería el límite de 2 TB de un almacén de datos de VMware. Este es el caso a partir de VSphere 5.0. Las versiones superiores de VSphere tienen límites mucho más altos.
- La necesidad de mejorar el rendimiento mediante la gestión de IO . Una razón más por la que puede querer mover archivos de datos es el rendimiento. Hay casos en los que se crea una base de datos con múltiples archivos de datos, todos sentados en un disco hasta que se hace evidente, a medida que crece la base de datos, que ha creado una "región caliente" en la capa de almacenamiento. Una solución sería crear nuevos archivos de datos y reconstruir índices agrupados, otra sería mover archivos de datos.
Escenario uno:Mover bases de datos de usuarios
Los pasos necesarios para mover una base de datos de usuario implican lo siguiente:
- Desconectar la base de datos
- Actualice el catálogo del sistema con la nueva ubicación
- Copie el archivo de datos físicamente en la nueva ubicación
- Poner la base de datos en línea
El Listado 1 muestra los comandos ejecutados para lograr estos pasos.
Listado 1 Moviendo archivos de datos
-- 1. Run the following statement to check the current location of files. SELECT name, physical_name AS CurrentLocation, state_desc FROM sys.master_files WHERE database_id = DB_ID(N'BranchDB'); -- 2. Take the database offline. ALTER DATABASE BranchDB SET OFFLINE; -- 3. Move the file or files to the new location (at OS level). -- 4. For each file moved, run the following statement. ALTER DATABASE BranchDB MODIFY FILE ( NAME = WWI_UserData, FILENAME = 'N:\MSSQL\Data\WWI_UserDataNew.ndf' ); -- 5. Run the following statement. ALTER DATABASE BranchDB SET ONLINE; -- 6. Verify the file change by running the following query. SELECT name, physical_name AS CurrentLocation, state_desc FROM sys.master_files WHERE database_id = DB_ID(N'BranchDB');
Es importante tener en cuenta que al desconectar una base de datos, la cantidad de sesiones activas puede retrasar el proceso. Programar un tiempo de inactividad para realizar esta tarea sería una buena idea. Durante dicho tiempo de inactividad, el propietario de la aplicación debe detener la conexión de los servicios de la aplicación a la base de datos antes de que el DBA intente desconectar la base de datos. Hay casos en los que no es tan conveniente desconectar la base de datos, entonces cerrar la instancia sería la mejor opción. En tal caso, el enfoque sería ligeramente diferente:
- Actualice el catálogo del sistema con la nueva ubicación
- Cerrar la instancia
- Copie el archivo de datos deseado físicamente a la nueva ubicación
- Iniciar la instancia
En ambos enfoques, el concepto es el mismo:implica actualizar el catálogo del sistema en la base de datos maestra y luego reubicar físicamente el archivo de datos deseado. En ambos casos, el archivo de datos debe cerrarse limpiamente. Echemos un vistazo a los pasos involucrados en el primer enfoque.

Fig. 1 Verificar la ubicación de los archivos de datos
El primer paso sería comprobar el estado de las cosas para empezar. Luego proceda a establecer la base de datos fuera de línea y modifique el catálogo del sistema.

Fig. 2 Establecer base de datos fuera de línea y modificar catálogo
Como se ve en la Fig. 3, una vez que actualizamos el catálogo, consultar sys.master_files nos dice la nueva ubicación en la que la base de datos maestra espera que esté el archivo de datos, ya sea que hayamos movido físicamente el archivo o no. En la Fig. 4, también vemos que no es posible poner la base de datos en línea sin primero mover físicamente el archivo a la nueva ubicación (y cambiar el nombre del archivo para que coincida con el nuevo nombre especificado en el catálogo).

Fig. 3 Nuevas ubicaciones de archivos

Fig. 4 Archivo faltante
También nos gustaría señalar que una vez que copiamos el archivo, perdemos los permisos anteriores en el archivo y SQL Server no podrá abrir el archivo cuando intentemos poner la base de datos en línea. Debemos editar los permisos del archivo y agregar otorgar a la cuenta NT SERVICE\MSSQLSERVER permisos completos en el archivo.

Fig. 5 Copie el archivo de datos

Fig. 6 Permisos en destino

Fig. 7a Permisos en la fuente

Fig. 7b Permisos en la fuente
Si intentáramos volver a poner la base de datos en línea sin estos permisos, obtendremos un error 0x5 (Acceso denegado). Si fuéramos a hacer algo como mover el archivo de datos usando un trabajo de agente, encontramos que la cuenta del Agente SQL Server adquiere la propiedad del archivo y podemos traer la base de datos solo porque la cuenta del Agente SQL Server es la misma que la Cuenta del Servidor SQL.

Fig. 8 Acceso denegado en nuevo archivo de datos
Suponiendo que estaba tratando de poner la base de datos en línea usando la GUI de SSMS, vería estos errores en el Visor de eventos, así como en el registro de errores de SQL Server si mira de cerca. Además, si estuviera utilizando el segundo enfoque (reiniciando toda la instancia), observaría que la base de datos se atascaría en la etapa de recuperación. Examinar el registro de errores le dirá lo que realmente está pasando.
Listado 2 Mover archivos de datos usando un trabajo de agente
/* ==Scripting Parameters== Source Server Version : SQL Server 2017 (14.0.3023) Source Database Engine Edition : Microsoft SQL Server Standard Edition Source Database Engine Type : Standalone SQL Server Target Server Version : SQL Server 2017 Target Database Engine Edition : Microsoft SQL Server Standard Edition Target Database Engine Type : Standalone SQL Server */ USE [msdb] GO /****** Object: Job [MoveDataFile] Script Date: 7/12/2018 12:33:55 AM ******/ BEGIN TRANSACTION DECLARE @ReturnCode INT SELECT @ReturnCode = 0 /****** Object: JobCategory [[Uncategorized (Local)]] Script Date: 7/12/2018 12:33:56 AM ******/ IF NOT EXISTS (SELECT name FROM msdb.dbo.syscategories WHERE name = N'[Uncategorized (Local)]' AND category_class = 1) BEGIN EXEC @ReturnCode = msdb.dbo.sp_add_category @class = N'JOB' ,@type = N'LOCAL' ,@name = N'[Uncategorized (Local)]' IF (@@error <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback END DECLARE @jobId BINARY(16) EXEC @ReturnCode = msdb.dbo.sp_add_job @job_name = N'MoveDataFile' ,@enabled = 1 ,@notify_level_eventlog = 0 ,@notify_level_email = 0 ,@notify_level_netsend = 0 ,@notify_level_page = 0 ,@delete_level = 0 ,@description = N'No description available.' ,@category_name = N'[Uncategorized (Local)]' ,@owner_login_name = N'sa' ,@job_id = @jobId OUTPUT IF (@@error <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback /****** Object: Step [MoveDataFile] Script Date: 7/12/2018 12:33:56 AM ******/ EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @job_id = @jobId ,@step_name = N'MoveDataFile' ,@step_id = 1 ,@cmdexec_success_code = 0 ,@on_success_action = 1 ,@on_success_step_id = 0 ,@on_fail_action = 2 ,@on_fail_step_id = 0 ,@retry_attempts = 0 ,@retry_interval = 0 ,@os_run_priority = 0 ,@subsystem = N'PowerShell' ,@command = N'Copy-Item -Path M:\MSSQL\Data\WWI_UserData1.ndf N:\MSSQL\Data\WWI_UserData1.ndf' ,@database_name = N'master' ,@flags = 0 IF (@@error <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback EXEC @ReturnCode = msdb.dbo.sp_update_job @job_id = @jobId ,@start_step_id = 1 IF (@@error <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback EXEC @ReturnCode = msdb.dbo.sp_add_jobserver @job_id = @jobId ,@server_name = N'(local)' IF (@@error <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback COMMIT TRANSACTION GOTO EndSave QuitWithRollback: IF (@@trancount > 0) ROLLBACK TRANSACTION EndSave: GO

Fig. 9 Permisos en el archivo de datos al usar el trabajo del agente

Fig. 10 Base de datos en línea
Automatización del proceso
Solo por diversión, podemos decidir usar el trabajo del Agente SQL Server para todo el proceso. Configuramos un paso de trabajo para cada paso de nuestro proceso. Esto puede ser útil si desea ser un DBA superestrella y programar una migración de este tipo durante la noche mientras se va a casa y se relaja con la familia. Definitivamente querrás asegurarte de configurar una notificación para que se active cuando el trabajo se realice correctamente, de modo que estés seguro de que realmente se realiza mientras estás fuera.
Listado 3 Realización de la tarea usando un trabajo de agente
/* ==Scripting Parameters== Source Server Version : SQL Server 2017 (14.0.3023) Source Database Engine Edition : Microsoft SQL Server Standard Edition Source Database Engine Type : Standalone SQL Server Target Server Version : SQL Server 2017 Target Database Engine Edition : Microsoft SQL Server Standard Edition Target Database Engine Type : Standalone SQL Server */ USE [msdb] GO /****** Object: Job [MoveDataFile] Script Date: 7/12/2018 12:46:47 AM ******/ BEGIN TRANSACTION DECLARE @ReturnCode INT SELECT @ReturnCode = 0 /****** Object: JobCategory [[Uncategorized (Local)]] Script Date: 7/12/2018 12:46:47 AM ******/ IF NOT EXISTS (SELECT name FROM msdb.dbo.syscategories WHERE name = N'[Uncategorized (Local)]' AND category_class = 1) BEGIN EXEC @ReturnCode = msdb.dbo.sp_add_category @class = N'JOB' ,@type = N'LOCAL' ,@name = N'[Uncategorized (Local)]' IF (@@error <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback END DECLARE @jobId BINARY(16) EXEC @ReturnCode = msdb.dbo.sp_add_job @job_name = N'MoveDataFile' ,@enabled = 1 ,@notify_level_eventlog = 0 ,@notify_level_email = 3 ,@notify_level_netsend = 0 ,@notify_level_page = 0 ,@delete_level = 0 ,@description = N'No description available.' ,@category_name = N'[Uncategorized (Local)]' ,@owner_login_name = N'sa' ,@notify_email_operator_name = N'DBA' ,@job_id = @jobId OUTPUT IF (@@error <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback /****** Object: Step [Set Database Offline] Script Date: 7/12/2018 12:46:47 AM ******/ EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @job_id = @jobId ,@step_name = N'Set Database Offline' ,@step_id = 1 ,@cmdexec_success_code = 0 ,@on_success_action = 3 ,@on_success_step_id = 0 ,@on_fail_action = 2 ,@on_fail_step_id = 0 ,@retry_attempts = 0 ,@retry_interval = 0 ,@os_run_priority = 0 ,@subsystem = N'TSQL' ,@command = N'ALTER DATABASE BranchDB SET OFFLINE;' ,@database_name = N'master' ,@flags = 0 IF (@@error <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback /****** Object: Step [MoveDataFile] Script Date: 7/12/2018 12:46:47 AM ******/ EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @job_id = @jobId ,@step_name = N'MoveDataFile' ,@step_id = 2 ,@cmdexec_success_code = 0 ,@on_success_action = 3 ,@on_success_step_id = 0 ,@on_fail_action = 2 ,@on_fail_step_id = 0 ,@retry_attempts = 0 ,@retry_interval = 0 ,@os_run_priority = 0 ,@subsystem = N'PowerShell' ,@command = N'Copy-Item -Path M:\MSSQL\Data\WWI_UserData1.ndf N:\MSSQL\Data\WWI_UserData1.ndf' ,@database_name = N'master' ,@flags = 0 IF (@@error <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback /****** Object: Step [ModifyFile and Bring Online] Script Date: 7/12/2018 12:46:47 AM ******/ EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @job_id = @jobId ,@step_name = N'ModifyFile and Bring Online' ,@step_id = 3 ,@cmdexec_success_code = 0 ,@on_success_action = 1 ,@on_success_step_id = 0 ,@on_fail_action = 2 ,@on_fail_step_id = 0 ,@retry_attempts = 0 ,@retry_interval = 0 ,@os_run_priority = 0 ,@subsystem = N'TSQL' ,@command = N' ALTER DATABASE BranchDB MODIFY FILE ( NAME = WWI_UserData, FILENAME = ''N:\MSSQL\Data\WWI_UserDataNew.ndf'' ); ALTER DATABASE BranchDB SET ONLINE;' ,@database_name = N'master' ,@flags = 0 IF (@@error <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback EXEC @ReturnCode = msdb.dbo.sp_update_job @job_id = @jobId ,@start_step_id = 1 IF (@@error <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback EXEC @ReturnCode = msdb.dbo.sp_add_jobserver @job_id = @jobId ,@server_name = N'(local)' IF (@@error <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback COMMIT TRANSACTION GOTO EndSave QuitWithRollback: IF (@@trancount > 0) ROLLBACK TRANSACTION EndSave: GO
Conclusión
En este artículo, hemos visto una forma de mover archivos de bases de datos de usuarios en SQL Server. También hemos visto la necesidad de asegurarnos de prestar atención a los permisos en el archivo de datos en la nueva ubicación para que no encontremos errores al volver a poner la base de datos en línea. También hemos visto que podemos poner todo esto en un trabajo del Agente SQL Server usando los subsistemas T-SQL y PowerShell. En un artículo posterior, veremos otros dos métodos para mover archivos de bases de datos a un nuevo volumen.
Lecturas adicionales:
Mover archivos de datos en SQL Server:parte 2