Mover múltiples bases de datos de C: a D: a la vez


17

Tengo SQL Server 2008 R2 con 323 bases de datos que consumen unos 14 GB en mi disco C: un SSD rápido.

Debido a que quiero recuperar algo de espacio en mi unidad C: me gustaría moverlos a mi unidad D:.

He encontrado este artículo de MSDN , pero ese parece ser el procedimiento para mover solo una base de datos.

¿Existe una forma automática o script para mover todas mis bases de datos a la vez?

Respuestas:


20

Yo uso Powershell para este tipo de trabajo. De hecho, uso Powershell para generar Powershell, porque tengo un script que recorrerá mis bases de datos y generará mi script de movimiento final. Tendrá que mover cada base de datos de una en una, pero esto al menos le ayudará a escribir el 90% del trabajo.

#load SMO
Add-PSSnapin SqlServerCmdletSnapin100
Add-PSSnapin SqlServerProviderSnapin100
#Added line if using SQL Server 2012 or later
Import-module SQLPS
[System.Reflection.Assembly]::LoadWithPartialName('Microsoft.SqlServer.SMO') | out-null

#Create server object and output filename 
$server = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Server "localhost"
$outputfile=([Environment]::GetFolderPath("MyDocuments"))+"\FileMover.ps1"

#set this for your new location
$newloc="X:\NewDBLocation"

#get your databases
$db_list=$server.Databases

#build initial script components
"Add-PSSnapin SqlServerCmdletSnapin100" > $outputfile
"Add-PSSnapin SqlServerProviderSnapin100" >> $outputfile
"Import-Module SQLPS" >> $outputfile 
"[System.Reflection.Assembly]::LoadWithPartialName('Microsoft.SqlServer.SMO') `"localhost`" | out-null" >> $outputfile
"`$server = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Server " >> $outputfile

foreach($db_build in $db_list)
{
    #only process user databases
    if(!($db_build.IsSystemObject))
    {
        #script out all the file moves
        "#----------------------------------------------------------------------" >> $outputfile
        "`$db=`$server.Databases[`""+$db_build.Name+"`"]" >> $outputfile

        $dbchange = @()
        $robocpy =@()
        foreach ($fg in $db_build.Filegroups)
        {
            foreach($file in $fg.Files)
            {
                $shortfile=$file.Filename.Substring($file.Filename.LastIndexOf('\')+1)
                $oldloc=$file.Filename.Substring(0,$file.Filename.LastIndexOf('\'))
                $dbchange+="`$db.FileGroups[`""+$fg.Name+"`"].Files[`""+$file.Name+"`"].Filename=`"$newloc`\"+$shortfile+"`""
                $robocpy+="ROBOCOPY `"$oldloc`" `"$newloc`" $shortfile /copyall /mov"

            }
        }

        foreach($logfile in $db_build.LogFiles)
        {
            $shortfile=$logfile.Filename.Substring($logfile.Filename.LastIndexOf('\')+1)
            $oldloc=$logfile.Filename.Substring(0,$logfile.Filename.LastIndexOf('\'))
            $dbchange+="`$db.LogFiles[`""+$logfile.Name+"`"].Filename=`"$newloc`\"+$shortfile+"`""
            $robocpy+="ROBOCOPY `"$oldloc`" `"$newloc`" $shortfile /copyall /mov"
        }

        $dbchange+="`$db.Alter()" 
        $dbchange+="Invoke-Sqlcmd -Query `"ALTER DATABASE ["+$db_build.Name+"] SET OFFLINE WITH ROLLBACK IMMEDIATE;`" -Database `"master`"" 

        $dbchange >> $outputfile
        $robocpy >> $outputfile

        "Invoke-Sqlcmd -Query `"ALTER DATABASE ["+$db_build.Name+"] SET ONLINE;`" -Database `"master`""  >> $outputfile
    }
}

El resultado será un script FileMover.ps1 en su carpeta MyDocuments que se verá así:

    Add-PSSnapin SqlServerCmdletSnapin100
    Add-PSSnapin SqlServerProviderSnapin100
    Import-Module SQLPS
    [System.Reflection.Assembly]::LoadWithPartialName('Microsoft.SqlServer.SMO') "localhost" | out-null
    $server = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Server 
    #----------------------------------------------------------------------
    $db=$server.Databases["AdventureWorks2012"]
    $db.FileGroups["PRIMARY"].Files["AdventureWorks2012_Data"].Filename="X:\NewDBLocation\AdventureWorks2012_Data.mdf"
    $db.LogFiles["AdventureWorks2012_Log"].Filename="X:\NewDBLocation\AdventureWorks2012_log.ldf"
    $db.Alter()
    Invoke-Sqlcmd -Query "ALTER DATABASE [AdventureWorks2012] SET OFFLINE WITH ROLLBACK IMMEDIATE;" -Database "master"
    ROBOCOPY "C:\DBData" "X:\NewDBLocation" AdventureWorks2012_Data.mdf /copyall /mov
    ROBOCOPY "C:\DBFiles\Log" "X:\NewDBLocation" AdventureWorks2012_log.ldf /copyall /mov
    Invoke-Sqlcmd -Query "ALTER DATABASE [AdventureWorks2012] SET ONLINE;" -Database "master"
    #----------------------------------------------------------------------
    $db=$server.Databases["AdventureWorks2012DW"]
    $db.FileGroups["PRIMARY"].Files["AdventureWorksDW2012_Data"].Filename="X:\NewDBLocation\AdventureWorksDW2012_Data.mdf"
    $db.LogFiles["AdventureWorksDW2012_Log"].Filename="X:\NewDBLocation\AdventureWorks2012DW_log.ldf"
    $db.Alter()
    Invoke-Sqlcmd -Query "ALTER DATABASE [AdventureWorks2012DW] SET OFFLINE WITH ROLLBACK IMMEDIATE;" -Database "master"
    ROBOCOPY "C:\DBData" "X:\NewDBLocation" AdventureWorksDW2012_Data.mdf /copyall /mov
    ROBOCOPY "C:\DBData" "X:\NewDBLocation" AdventureWorks2012DW_log.ldf /copyall /mov
    Invoke-Sqlcmd -Query "ALTER DATABASE [AdventureWorks2012DW] SET ONLINE;" -Database "master"

...

Advertencias

  • El script mueve todos los archivos, sin importar su ubicación de origen, al mismo destino. Deberá ajustar las rutas de ubicación personalizadas.
  • El script está diseñado para ejecutarse en el servidor en el que necesita mover los archivos (ver todos los usos de localhost '). Reemplace localhost con su nombre de instancia si ejecuta esto de forma remota.
  • El usuario con el que ejecuta esto necesita acceso a todas las rutas de carpetas involucradas en el movimiento, tanto para actualizar la información del nombre de archivo del servidor SQL como para mover los archivos.
  • Utilizo InvokeSQLCmd para la ejecución fuera de línea / en línea debido a la naturaleza funky de los métodos .SetOffline () y .SetOnline. He encontrado que esto es más confiable.

@ MikeFal Vi que aprobaste la edición. Dado que la pregunta está etiquetada con 2008R2, ¿no debería ser más evidente la adición (negrita o algo así)? (No tengo idea, pero supongo que podría no funcionar o romper algo en una versión que no sea 2012).
ypercubeᵀᴹ

1
Pensé en eso y lo ejecuté en SQL Server 2012 R2: arroja errores con los cmdlets Add-PSSnapin SqlServerCmdletSnapin100, pero procesa siempre que tenga el SQLPS Import-Module incluido antes de la parte principal del script El proceso se ejecuta. Técnicamente, esto debería tener un mejor error al verificar esto, pero pensé que sería una buena edición rápida para ayudar a alguien que tal vez no pudo encontrar la necesidad del SQLPS Import-Module si están en un Versión más nueva.
Chad Rexin

1
Gracias una tonelada. Pequeño problema sin embargo. Los nombres de los archivos de robocopy no se citan aquí. Si el usuario ha creado nombres de bases de datos con espacios, no funciona del todo bien.
Tim Brigham el

7

Puede utilizar los métodos Modificar archivo de base de datos o Separar / Adjuntar.

Nota: Ambos requerirán algún tiempo de inactividad, por lo que debe hacerse durante la ventana de mantenimiento.

Esto supone que tiene la misma estructura de directorio en la nueva unidad, por ejemplo, C: \ data \ y D: \ Data.

- utilizando la base de datos Alter con el método Modify (preferido)

SET NOCOUNT ON

DECLARE @datafile VARCHAR(255)
    ,@logfile VARCHAR(255)
    ,@dbid TINYINT
    ,@SQLText VARCHAR(max)
    ,@dbname VARCHAR(255)
    ,@sqltext1 VARCHAR(max)
    ,@SQLText2 VARCHAR(max)

--2. Prepare for modify
IF EXISTS (
        SELECT 1
        FROM tempdb..sysobjects
        WHERE NAME LIKE '%#filetable%'
        )
BEGIN
    DROP TABLE #filetable
END

CREATE TABLE #filetable (
    mdf VARCHAR(255)
    ,ldf VARCHAR(255)
    ,dbid TINYINT
    ,dbname VARCHAR(100)
    ,fileid TINYINT
    ,logicalname SYSNAME
    )

--
INSERT #filetable (
    mdf
    ,dbid
    ,fileid
    ,logicalname
    )
SELECT physical_name
    ,database_id
    ,data_space_id
    ,NAME
FROM sys.master_files
WHERE data_space_id = 1

INSERT #filetable (
    ldf
    ,dbid
    ,fileid
    ,logicalname
    )
SELECT physical_name
    ,database_id
    ,data_space_id
    ,NAME
FROM sys.master_files
WHERE data_space_id = 0

UPDATE u
SET u.dbname = s.NAME
FROM #filetable u
INNER JOIN master..sysdatabases s ON u.dbid = s.dbid

UPDATE #filetable
SET mdf = replace(mdf, 'C:', 'D:')
    ,ldf = replace(ldf, 'C:', 'D:')
FROM #filetable

SELECT @dbid = min(dbid)
FROM #filetable
WHERE dbid > 4

WHILE @dbid IS NOT NULL
BEGIN
    SELECT @SQLText = 'alter database [' + dbname + '] MODIFY FILE (Name = ' + logicalname + ' , FileName = N''' + ldf + ''');'
    FROM #filetable
    WHERE dbid = convert(VARCHAR, @dbid)
        AND fileid = 0 -- Log file

    PRINT @SQLText

    --Exec(@SQLText)
    SELECT @SQLText2 = 'alter database [' + dbname + '] MODIFY FILE (Name = ' + logicalname + ' , FileName = N''' + mdf + ''');'
    FROM #filetable
    WHERE dbid = convert(VARCHAR, @dbid)
        AND fileid = 1 -- data file

    PRINT @SQLText2

    --Exec(@SQLText)
    SELECT @dbid = min(dbid)
    FROM #filetable
    WHERE dbid > 4
        AND dbid > @dbid
END

--- usando el método Old Detach / Attach (no preferido, pero la gente todavía lo usa ... desafortunadamente lo usé recientemente en un servidor que no es de producción).

DECLARE @datafile VARCHAR(255)
    ,@logfile VARCHAR(255)
    ,@dbid TINYINT
    ,@SQLText VARCHAR(8000)
    ,@dbname VARCHAR(255)
    ,@SQLText2 VARCHAR(8000)

--2. Detach All Local Databases and prepare for Attach
IF EXISTS (
        SELECT 1
        FROM tempdb..sysobjects
        WHERE NAME LIKE '%#filetable%'
        )
BEGIN
    DROP TABLE #filetable
END

CREATE TABLE #filetable (
    mdf VARCHAR(255)
    ,ldf VARCHAR(255)
    ,dbid TINYINT
    ,dbname VARCHAR(100)
    ,fileid TINYINT
    )

--
INSERT #filetable (
    mdf
    ,dbid
    ,fileid
    )
SELECT physical_name
    ,database_id
    ,data_space_id
FROM sys.master_files
WHERE data_space_id = 1

INSERT #filetable (
    ldf
    ,dbid
    ,fileid
    )
SELECT physical_name
    ,database_id
    ,data_space_id
FROM sys.master_files
WHERE data_space_id = 0

UPDATE u
SET u.dbname = s.NAME
FROM #filetable u
INNER JOIN master..sysdatabases s ON u.dbid = s.dbid

UPDATE #filetable
SET mdf = replace(mdf, 'C:', 'D:')
    ,ldf = replace(ldf, 'C:', 'D:')
FROM #filetable

SELECT @dbid = min(dbid)
FROM #filetable
WHERE dbid > 4

WHILE @dbid IS NOT NULL
BEGIN
    SELECT @SQLText = 'alter database [' + dbname + ']'
    FROM #filetable
    WHERE dbid = convert(VARCHAR, @dbid)

    SELECT @SQLText = @SQLText + CHAR(10) + ' set single_user with rollback immediate;'

    SELECT @SQLText = @SQLText + CHAR(10) + ' exec master..sp_detach_db ' + dbname
    FROM #filetable
    WHERE dbid = convert(VARCHAR, @dbid)

    PRINT @SQLText

    --Exec(@SQLText)
    SELECT @SQLText2 = 'exec master..sp_attach_db ''' + dbname + ''''
    FROM #filetable
    WHERE dbid = @dbid

    SELECT @SQLText2 = @SQLText2 + ',''' + mdf + ''''
    FROM #filetable
    WHERE dbid = @dbid
        AND mdf IS NOT NULL

    SELECT @SQLText2 = @SQLText2 + ',''' + ldf + ''''
    FROM #filetable
    WHERE dbid = @dbid
        AND ldf IS NOT NULL

    PRINT @SQLText2

    --Exec(@SQLText)
    SELECT @dbid = min(dbid)
    FROM #filetable
    WHERE dbid > 4
        AND dbid > @dbid
END

DROP TABLE #filetable

6

La única forma en que sé hacer múltiples bases de datos a la vez sería escribir el movimiento para múltiples bases de datos a la vez.

ALTER DATABASE database_nameA SET OFFLINE WITH ROLLBACK IMMEDIATE;
ALTER DATABASE database_nameB SET OFFLINE WITH ROLLBACK IMMEDIATE;
ALTER DATABASE database_nameC SET OFFLINE WITH ROLLBACK IMMEDIATE;
-------

Aquí puede mover los archivos manualmente o escribir un script para hacerlo. Posiblemente usando xp_cmdshell o alguna herramienta. Sin embargo, probablemente sea más fácil mover los archivos a mano. Marque un montón de ellos, luego arrastre y suelte.

-------
ALTER DATABASE database_nameA MODIFY FILE ( NAME = logical_name, FILENAME = 'new_path\os_file_name' );
ALTER DATABASE database_nameB MODIFY FILE ( NAME = logical_name, FILENAME = 'new_path\os_file_name' );
ALTER DATABASE database_nameC MODIFY FILE ( NAME = logical_name, FILENAME = 'new_path\os_file_name' );

ALTER DATABASE database_nameA SET ONLINE;
ALTER DATABASE database_nameB SET ONLINE;
ALTER DATABASE database_nameC SET ONLINE;

Por supuesto, si mueve el archivo de datos y el archivo de registro, debe asegurarse de hacer la parte MODIFICAR ARCHIVO para cada parte.


0
------------------------------
--erezbensimon@gmail.com - July 2016


use master;
go 

SET NOCOUNT ON

print '----------------------------------------------------------------------------------'
print '--Script for Moving Multiple database files to a new drive / ' + CONVERT(varchar(256),getdate() )
print '----------------------------------------------------------------------------------'
print ''


DECLARE @dbname nvarchar(128)
DECLARE @DestPath nvarchar(256)


--Set here the new destination path of the file
set @DestPath =  'T:\Data\'

------------------------------------------------
--Filter: HD Databases
------------------------------------------------
DECLARE DBList_cursor CURSOR FOR 

Select name from sys.databases

--where name like '<FIlter Something>'

----------------------------------------------

OPEN DBList_cursor

FETCH NEXT FROM DBList_cursor 
 INTO @dbname

WHILE @@FETCH_STATUS = 0
BEGIN

  declare @output_script varchar(max) --Output of the generated script
  declare @mdf_orig_path nvarchar(256) --Original datbase file path
  declare @cmdstring nvarchar(256) --Command String
  declare @CursorDeclare varchar(max) --Cursor declaration command
  declare @Originalfilename varchar(max) -- local @CursorDeclare command
  declare @filename varchar(max) -- local @CursorDeclare command
  declare @LogicalFileaame varchar(max) -- Logical FileName

  --Set null into @output script
  set @output_script=''
  --Generate Databse Cursor declaration command
  set @CursorDeclare='DECLARE DBFiles_cursor CURSOR FOR select [filename], [name] from '+ @dbname + '.sys.sysfiles'
  --Cursor Declaration
  execute (@CursorDeclare) 


  OPEN DBFiles_cursor
  FETCH NEXT FROM DBFiles_cursor INTO @filename, @LogicalFileaame

  --For RollBack Option
  select @Originalfilename = @filename

  --Modify Physical FileName
  if (@filename like '%.mdf') begin 
         select @mdf_orig_path = @filename
          IF(CHARINDEX('\', @filename) > 0)
          select @filename = RIGHT(@filename, CHARINDEX('\', REVERSE(@filename)) -1) 

         select @filename = @DestPath + @filename
         select @cmdstring = ' ''copy' + ' ' + '"'+ @mdf_orig_path + '"' + ' ' + '"' + @filename +'"' + ''''
  --Get Logical FileNAme


  end




print CHAR(10)
print '-----------------------------------------'
print  @dbname 
print '-----------------------------------------'
print CHAR(10)
print 'print  ''Start'' + CONVERT(varchar(256), getdate() ) '
print '---Offline Database' + @dbname      
print 'ALTER DATABASE ' + @dbname + ' SET OFFLINE WITH ROLLBACK IMMEDIATE' + CHAR(10) + 'GO'
print 'exec master..xp_cmdshell' + ' ' + @cmdstring + CHAR(10)
print '--For RollBack Use this:ALTER DATABASE ' + @dbname +' MODIFY FILE ( NAME =' + @LogicalFileaame +', FILENAME =' + @Originalfilename + ')' + CHAR(10)
print 'ALTER DATABASE ' + @dbname +' MODIFY FILE ( NAME =' + @LogicalFileaame +', FILENAME =' + '''' + @DestPath + @dbname + '.mdf'' )' +CHAR(10)


print '---ONline Database' + @dbname
print 'ALTER DATABASE ' + @dbname + ' SET ONLINE WITH
ROLLBACK IMMEDIATE
GO' 

  WHILE @@FETCH_STATUS = 0
  BEGIN   
   set @output_script=@output_script+' (FILENAME = '''+ @filename +'''),' 
      FETCH NEXT FROM DBFiles_cursor INTO @filename, @LogicalFileaame
  END

set @output_script=SUBSTRING(@output_script,0,len(@output_script))

CLOSE DBFiles_cursor
DEALLOCATE DBFiles_cursor

FETCH NEXT FROM DBList_cursor 
INTO @dbname

END 


CLOSE DBList_cursor
DEALLOCATE DBList_cursor

Debe formatear su código (=> dba.stackexchange.com/help/formatting ) y agregar algunas palabras para explicar cómo responde la pregunta ... (=> dba.stackexchange.com/help/how-to-answer )
Julien Vavasseur

0

Este script devolverá un lote de declaraciones que puede ejecutar.

SELECT d.name as db, f.name, physical_name, f.state_desc,
'ALTER DATABASE ['+d.name+'] MODIFY FILE (name='''+f.name+''' ,filename='''+replace(physical_name,'C:\database','D:\whatever')+'''); ' as DetachCommand,
'ALTER DATABASE ['+d.name+'] SET ONLINE' as ReattachCommand
from sys.master_files f 
inner join sys.databases d on d.database_id=f.database_id
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.