Verschieben Sie mehrere Datenbanken gleichzeitig von C: nach D :.


17

Ich habe SQL Server 2008 R2 mit 323 Datenbanken, die ungefähr 14 GB auf meinem Laufwerk C: verbrauchen, einer schnellen SSD.

Da ich auf meinem C: -Laufwerk Speicherplatz freigeben möchte, möchte ich diese auf mein D: -Laufwerk verschieben.

Ich habe diesen MSDN-Artikel gefunden , aber das scheint die Vorgehensweise zu sein, um nur eine Datenbank zu verschieben.

Gibt es eine automatische Methode oder ein Skript zum gleichzeitigen Verschieben aller meiner Datenbanken?

Antworten:


20

Ich benutze Powershell für diese Art von Arbeit. Tatsächlich benutze ich Powershell, um Powershell zu generieren, da ich ein Skript habe, das meine Datenbanken durchläuft und mein endgültiges Verschiebungsskript generiert. Sie müssen jede Datenbank einzeln verschieben, dies hilft Ihnen jedoch, mindestens 90% der Arbeit per Skript auszuführen.

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

Die Ausgabe ist ein FileMover.ps1-Skript in Ihrem MyDocuments-Ordner, das ungefähr so ​​aussieht:

    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"

...

Vorbehalte

  • Das Skript verschiebt alle Dateien, unabhängig vom Quellspeicherort, an dasselbe Ziel. Für benutzerdefinierte Standortpfade müssen Sie Anpassungen vornehmen.
  • Das Skript läuft auf dem Server, auf den Sie die Dateien verschieben müssen (siehe alle Verwendungen von localhost). Ersetzen Sie localhost durch Ihren Instanznamen, wenn Sie dies remote ausführen.
  • Der Benutzer, den Sie ausführen, benötigt Zugriff auf alle Ordnerpfade, die an der Verschiebung beteiligt sind, sowohl zum Aktualisieren der SQL Server-Dateinameninformationen als auch zum Verschieben der Dateien.
  • Ich verwende InvokeSQLCmd für die Offline- / Online-Ausführung, da die Methoden .SetOffline () und .SetOnline irre sind. Ich habe festgestellt, dass dies zuverlässiger ist.

@MikeFal Ich habe gesehen, dass du der Bearbeitung zugestimmt hast. Da die Frage mit 2008R2 markiert ist, sollte der Zusatz nicht offensichtlicher sein (fett oder so)? (Ich habe keine Ahnung, aber ich vermute, dass es in einer anderen Version als 2012 möglicherweise nicht funktioniert oder etwas kaputt geht.)
ypercubeᵀᴹ

1
Ich habe darüber nachgedacht und es auf SQL Server 2012 R2 ausgeführt - es wirft Fehler mit den Cmdlets Add-PSSnapin SqlServerCmdletSnapin100, aber es wird verarbeitet, solange Sie das Import-Module SQLPS vor dem Hauptteil des Skripts enthalten haben Prozess läuft. Technisch sollte dies eine bessere Fehlerprüfung haben, aber ich dachte mir, dass es eine gute schnelle Bearbeitung sein würde, um jemand anderem zu helfen, der möglicherweise nicht in der Lage ist, die Notwendigkeit für das Import-Modul SQLPS zu finden, wenn er sich in einem befindet neuere Version.
Chad Rexin

1
Danke vielmals. Kleines Problem. Die Namen der Robocopy-Dateien werden hier nicht in Anführungszeichen gesetzt. Wenn Sie Datenbanknamen mit Leerzeichen erstellt haben, funktioniert dies nicht richtig.
Tim Brigham

7

Sie können entweder die Modify File-Methode "Alter database" oder die Detach / Attach-Methode verwenden.

Hinweis: Beide erfordern einige Ausfallzeiten, die während des Wartungsfensters durchgeführt werden müssen.

Dies setzt voraus, dass Sie dieselbe Verzeichnisstruktur auf dem neuen Laufwerk haben, z. B. C: \ data \ und D: \ Data.

- Verwenden der Methode "Datenbank ändern" mit "Ändern" (bevorzugte Methode)

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

--- mit Old Detach / Attach-Methode (nicht bevorzugt, aber immer noch Leute verwenden es .. leider habe ich es vor kurzem auf einem Non-Prod-Server verwendet).

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

Die einzige Möglichkeit, mehrere DBs gleichzeitig zu erstellen, besteht darin, die Verschiebung für mehrere DBs gleichzeitig zu skripten.

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

Hier können Sie die Dateien entweder manuell verschieben oder ein Skript dafür schreiben. Möglicherweise mit xp_cmdshell oder einem Tool. Wahrscheinlich ist es einfacher, die Dateien nur von Hand zu verschieben. Markieren Sie einige von ihnen und ziehen Sie sie per Drag & Drop.

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

Wenn Sie die Datendatei und die Protokolldatei verschieben, müssen Sie natürlich sicherstellen, dass Sie den Teil DATEI ÄNDERN für jeden Teil ausführen.


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

Sie sollten Ihren Code formatieren (=> dba.stackexchange.com/help/formatting ) und einige Wörter hinzufügen, um zu erklären, wie die Frage beantwortet wird ... (=> dba.stackexchange.com/help/how-to-answer )
Julien Vavasseur

0

Dieses Skript gibt eine Reihe von Anweisungen zurück, die Sie ausführen können.

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
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.