Answers:
我使用Powershell进行此类工作。实际上,我使用Powershell生成Powershell,因为我有一个脚本可以遍历数据库并生成最终的移动脚本。您必须一次移动一个数据库,但这至少可以帮助您编写90%的工作脚本。
#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
}
}
输出将是MyDocuments文件夹中的FileMover.ps1脚本,如下所示:
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"
...
注意事项
您可以使用Alter database Modify File或Detach / Attach方法。
注意:两者都需要一些停机时间,因此必须在维护时段内完成。
假设您在新驱动器上具有相同的目录结构,例如C:\ data \和D:\ Data。
-将Alter数据库与Modify方法一起使用(首选)
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
---使用Old Detach / Attach方法(不是首选方法,但仍然有人使用它。不幸的是,我最近在NON产品服务器上使用了它)。
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
我知道一次执行多个数据库的唯一方法是一次编写多个数据库的移动脚本。
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;
-------
在这里,您可以手动移动文件,也可以编写脚本来完成。可能使用xp_cmdshell或某些工具。不过,手动移动文件可能更容易。标记一堆,然后拖放。
-------
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;
当然,如果移动数据文件和日志文件,则必须确保对每个部分都执行“修改文件”部分。
------------------------------
--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
该脚本将返回一批可以运行的语句。
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