我将简要介绍如何在当前项目中组织数据库的克隆(从一个备份创建多个数据库实例)。 该方法可以节省时间和硬盘空间。
情况:数据库很厚(例如100 GB)。 我想为每个开发人员单独拥有一个包含所有数据的数据库,而不要在上面花费TB的磁盘。 以下是使用Powershell的Windows MSSQL解决方案。
我遇到了Redgate SQL Clone实用程序。 该站点
对其工作方式进行了
描述 。 最重要的是要使用这样的东西:区分虚拟硬盘。 这就翻译成俄语,称为“差异虚拟磁盘”,即仅存储与“父”磁盘有关的差异的磁盘。
细节剪下
工作方案如下:
- 我们创建并插入普通的虚拟磁盘(以后将成为父磁盘)。
- 我们创建一个数据库实例,从该数据库实例进行克隆。 我们清理专业数据,为测试环境中的工作完全准备数据库。 我们将数据库文件放在虚拟磁盘上。
- 断开数据库与服务器的连接。 我们断开虚拟磁盘。
- 创建差异磁盘。 我们连接到系统。 我们将数据库从该磁盘连接到sql服务器。
- 重复步骤4,直到达到和谐数量的数据库为止。
将不描述创建父磁盘的过程,因为 这可以通过磁盘管理图形界面手动完成。 好吧,或者谷歌命令,并补充文章中给出的脚本。
注意时间:
在Windows 10和Windows Server 2016中,有powershell命令行开关
New-VHD 。 对于使用以前版本服务器的用户,有一个diskpart实用程序。 使用它进行自动化工作不是很方便,因为 在输入处,它会收到一个包含要执行命令的文件。
注意二:
因为 由于数据库文件放在差异磁盘上,因此这种解决方案的性能远远达不到标准。 事实证明,它具有多个间接级别:记录进入数据库,该数据库位于虚拟磁盘上,该磁盘将差异存储在Jack所建房屋中。 我没有具体的性能指标(因为在我们的测试电路中,这并不是第一个问题)。 如果有人测量了多少读/写速度,我将不胜感激。
注意三:
因为 脚本并不是为了广泛使用而提供的,例如,脚本的曲率不断提高,并且与MSSQL的绑定紧密。
我们将初始化一些变量:$server = "server"; $db_file_name = "db_file_name"; $root_path = "path to folder with disks"; $cred = try { Get-StoredCredential -Target "$server\Administrator"; } catch { Get-Credential -Message "server windows user" -UserName "$server\Administrator" } $db_cred = $(try { Get-StoredCredential -Target "$server\sa"; } catch { Get-Credential -Message "sql server user" -UserName "sa" }).GetNetworkCredential(); $session = New-PSSession -ComputerName $server -Credential $cred;
因为 该脚本在开发人员的计算机上运行,并且所有操作都在使用sql server的计算机上执行,假定已配置了powershell远程处理。 所有命令均在打开的会话中执行。
Get-StoredCredential是一个命令行工具,用于将凭据保存在本地计算机上(单独安装)。 原则上,您可以不使用它,这就是为什么将它包装在try / catch中的原因。
以下是diskpart脚本执行代码: function run_script([string]$script, [bool]$suppress_output = $false) { $result = Invoke-Command -Session $session -ArgumentList $script -ScriptBlock { param($script) $script.Split("`r`n") | % { Write-Host $_.Trim() }; Out-File -FilePath "tmp" -InputObject $script -Encoding ascii return diskpart /s "tmp" } if($suppress_output) { return $result; } else { $result | ? { !$_.Contains("Microsoft") -and $_ -ne "" } | Write-Host } }
我通过SQLCMD执行的Sql命令: function run_sql([string]$sql) { Write-Host $sql SQLCMD -S $server -d master -U $($db_cred.UserName) -P $($db_cred.Password) -Q $sql }
创建差异磁盘: run_script "create vdisk file=`"$root_path\$name.vhdx`" parent=`"$root_path\parent_disk.vhdx`""
接下来,连接磁盘和数据库: $disk_letter = Invoke-Command -Session $session -ScriptBlock { ls function:[dz]: -n | ?{ !(test-path $_) } | select -Last 1; } $volumes = run_script "list volume" $true $disks = run_script "list disk" $true $script = " sel vdisk file=`"$current_path\$db_name.vhdx`" attach vdisk"; run_script $script; $disks_after = run_script "list disk" $true $new_disk = $($disks_after | ? { $_ -notin $disks } ) Write-Host $new_disk $new_disk -match "\d+" $diskId = $Matches[0] $script = " select disk $diskId online disk"; run_script $script $volumes_after = run_script "list volume" $true # get added disk $new_volume = $($volumes_after | ? { $_ -notin $volumes } ) Write-Host $new_volume $new_volume -match "\d+" $volumeId = $Matches[0] $script = " select volume $volumeId assign letter=$disk_letter"; run_script $script run_script "list volume"; run_script "list vdisk"; $atach_script = "CREATE DATABASE $db_name ON (FILENAME = '$disk_letter\$db_file_name.mdf'),(FILENAME = '$disk_letter\$db_file_name.ldf') FOR ATTACH"; run_sql "$atach_script"
这段“ ls函数:[dz]:-n”只是获取驱动器盘符列表的一种魔术。 它是如何工作的-不知道,是从stackoverflow复制的。
在上面的代码中,最大的困难是获取生成的虚拟磁盘并将其放在特定的字母上。 他还需要预先在线进行。
断开驱动器的连接稍微容易一些: run_sql " ALTER DATABASE $name SET OFFLINE WITH ROLLBACK IMMEDIATE GO sp_detach_db $name"; $script = "select vdisk file=`"$root_path\$name.vhdx`" detach vdisk "; run_script $script
放在一起: param( [ValidateSet("detach_all", "attach_all_available", "create_new", "attach_db", "detach_db", "remove_file")][Parameter(mandatory=$true)][string] $mode, [string] $name ) function run_sql([string]$sql) { Write-Host $sql SQLCMD -S $server -d master -U $($db_cred.UserName) -P $($db_cred.Password) -Q $sql } function run_script([string]$script, [bool]$suppress_output = $false) { $result = Invoke-Command -Session $session -ArgumentList $script -ScriptBlock { param($script) $script.Split("`r`n") | % { Write-Host $_.Trim() }; Out-File -FilePath "tmp" -InputObject $script -Encoding ascii return diskpart /s "tmp" } if($suppress_output) { return $result; } else { $result | ? { !$_.Contains("Microsoft") -and $_ -ne "" } | Write-Host } } function attach_disk([string]$db_name, [string]$current_path) { $disk_letter = Invoke-Command -Session $session -ScriptBlock { ls function:[dz]: -n | ?{ !(test-path $_) } | select -Last 1; } $volumes = run_script "list volume" $true $disks = run_script "list disk" $true $script = " sel vdisk file=`"$current_path\$db_name.vhdx`" attach vdisk"; run_script $script; $disks_after = run_script "list disk" $true $new_disk = $($disks_after | ? { $_ -notin $disks } ) Write-Host $new_disk $new_disk -match "\d+" $diskId = $Matches[0] $script = " select disk $diskId online disk"; run_script $script $volumes_after = run_script "list volume" $true # get added disk $new_volume = $($volumes_after | ? { $_ -notin $volumes } ) Write-Host $new_volume $new_volume -match "\d+" $volumeId = $Matches[0] $script = " select volume $volumeId assign letter=$disk_letter"; run_script $script run_script "list volume"; run_script "list vdisk"; $atach_script = "CREATE DATABASE $db_name ON (FILENAME = '$disk_letter\$db_file_name.mdf'),(FILENAME = '$disk_letter\$db_file_name.ldf') FOR ATTACH"; run_sql "$atach_script" } $server = "server"; $db_file_name = "db_file_name"; $cred = try { Get-StoredCredential -Target "$server\Administrator"; } catch { Get-Credential -Message "server windows user" -UserName "$server\Administrator" } $db_cred = $(try { Get-StoredCredential -Target "$server\sa"; } catch { Get-Credential -Message "sql server user" -UserName "sa" }).GetNetworkCredential(); $session = New-PSSession -ComputerName $server -Credential $cred; $root_path = "path to folder with disks"; $files = Invoke-Command -Session $session -ArgumentList $root_path -ScriptBlock { param($root_path) Get-ChildItem -Filter "*.vhdx" -Path $root_path } switch ($mode) { "detach_all" { $files ` | % { Write-Host $("*"*40) `r`n $_.FullName `r`n; $_ } ` | % { " ALTER DATABASE $($_.Name.Replace('.vhdx', '')) SET OFFLINE WITH ROLLBACK IMMEDIATE GO sp_detach_db $($_.Name.Replace('.vhdx', ''))" } ` | % { run_sql "$_" } $files ` | % { Write-Host $("*"*40) `r`n $_.FullName `r`n; $_ } ` | % { run_script "select vdisk file=`"$($_.FullName)`" detach vdisk " } break; } "attach_all_available" { $files | % { $_.Name.Replace('.vhdx', '') } | ? { $_ -ne "parent_disk" } | % { attach_disk $_ $root_path } break; } "attach_db" { attach_disk $name $root_path break; } "detach_db" { run_sql " ALTER DATABASE $name SET OFFLINE WITH ROLLBACK IMMEDIATE GO sp_detach_db $name"; $script = "select vdisk file=`"$root_path\$name.vhdx`" detach vdisk "; run_script $script break; } "create_new" { $script = "create vdisk file=`"$root_path\$name.vhdx`" parent=`"$root_path\parent_disk.vhdx`"" run_script $script attach_disk $name $root_path; break; } "remove_file" { Invoke-Command -Session $session -ArgumentList $name,$root_path -ScriptBlock { param($name, $root_path) Remove-Item -Path "$root_path\$name.vhdx" } } } Remove-PSSession $session
到达时间:
如果您重新启动服务器,那么您将痛苦地向sql服务器解释这些数据库不存在,因此您需要重新连接它们。
二:
作者当然检查了他的测试电路上的命令,但无意保证任何内容(尤其是其性能)。 风险自负。
总计:
启动额外的测试数据库需要花费几分钟和40MB磁盘空间。 因此,对于每个开发人员来说,组织他们自己的数据库实例都更加方便。
可选的:
可以使用相同的脚本来引发数据库以进行集成测试。
我希望这对某人有用。