Déplacer plusieurs bases de données de C: vers D: à la fois


17

J'ai SQL Server 2008 R2 avec 323 bases de données consommant environ 14 Go sur mon lecteur C:, un SSD rapide.

Parce que je veux récupérer de l'espace sur mon lecteur C:, je voudrais les déplacer vers mon lecteur D :.

J'ai trouvé cet article MSDN , mais cela semble être la procédure pour déplacer une seule base de données.

Existe-t-il un moyen automatique ou un script pour déplacer toutes mes bases de données en même temps?

Réponses:


20

J'utilise Powershell pour ce genre de travail. En fait, j'utilise Powershell pour générer Powershell, car j'ai un script qui va parcourir mes bases de données et générer mon script de déplacement final. Vous devrez déplacer chaque base de données une par une, mais cela vous aidera au moins à scénariser 90% du travail.

#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
    }
}

La sortie sera un script FileMover.ps1 dans votre dossier MyDocuments qui ressemble à ceci:

    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"

...

Avertissements

  • Le script déplace tous les fichiers, quel que soit leur emplacement source, vers la même destination. Vous devrez modifier les chemins de localisation personnalisés.
  • Le script est conçu pour s'exécuter sur le serveur sur lequel vous devez déplacer les fichiers (voir toutes les utilisations de localhost '). Remplacez localhost par votre nom d'instance si vous l'exécutez à distance.
  • L'utilisateur sous lequel vous l'exécutez doit avoir accès à tous les chemins de dossier impliqués dans le déplacement, à la fois pour mettre à jour les informations de nom de fichier du serveur SQL et pour déplacer les fichiers.
  • J'utilise InvokeSQLCmd pour une exécution hors ligne / en ligne en raison de la nature géniale des méthodes .SetOffline () et .SetOnline. J'ai trouvé que c'était plus fiable.

@MikeFal J'ai vu que vous aviez approuvé la modification. Puisque la question est étiquetée avec 2008R2, l'ajout ne devrait-il pas être plus évident (gras ou quelque chose)? (Je n'en ai aucune idée, mais je suppose que cela pourrait ne pas fonctionner ou casser quelque chose dans une version autre que 2012).
ypercubeᵀᴹ

1
J'y ai pensé et je l'ai exécuté sur SQL Server 2012 R2 - cela génère des erreurs avec les applets de commande Add-PSSnapin SqlServerCmdletSnapin100, mais traite aussi longtemps que le module d'importation SQLPS est inclus avant la partie principale du script le processus s'exécute. Techniquement, cela devrait avoir une meilleure vérification des erreurs autour de cela, mais je me suis dit que ce serait une bonne modification rapide pour aider quelqu'un d'autre qui n'était peut-être pas en mesure de trouver le besoin du module d'importation SQLPS s'il est sur un version plus récente.
Chad Rexin

1
Merci beaucoup. Petit problème cependant. Les noms de fichiers robocopy ne sont pas cités ici. Si vous avez créé des noms de base de données avec des espaces, cela ne fonctionne pas très bien.
Tim Brigham

7

Vous pouvez utiliser les méthodes Modifier la base de données Modifier le fichier ou Détacher / Attacher.

Remarque: les deux nécessiteront un certain temps d'arrêt, donc doit être fait pendant la fenêtre de maintenance.

Cela suppose que vous avez la même structure de répertoires sur le nouveau lecteur, par exemple C: \ data \ et D: \ Data.

- utilisation de la base de données Alter avec la méthode Modify (préférée)

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

--- en utilisant la méthode Old Detach / Attach (pas préféré, mais les gens l'utilisent encore .. malheureusement je l'ai utilisé récemment sur un serveur NON prod).

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 seule façon que je sache de faire plusieurs bases de données à la fois serait de scripter le déplacement de plusieurs bases de données à la fois.

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

Ici, vous pouvez soit déplacer les fichiers manuellement, soit écrire un script pour le faire. Peut-être en utilisant xp_cmdshell ou un outil. Probablement plus facile de déplacer simplement les fichiers à la main. Marquez-en un tas, puis glissez-déposez.

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

Bien sûr, si vous déplacez le fichier de données et le fichier journal, vous devez vous assurer de faire la partie MODIFY FILE pour chaque partie.


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

Vous devez formater votre code (=> dba.stackexchange.com/help/formatting ) et ajouter quelques mots afin d'expliquer comment cela répond à la question ... (=> dba.stackexchange.com/help/how-to-answer )
Julien Vavasseur

0

Ce script renverra un lot d'instructions que vous pouvez exécuter.

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
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.