Opção de clonagem de banco de dados para desenvolvimento / teste

Descreverei brevemente como a clonagem do banco de dados foi organizada (a criação de várias instâncias do banco de dados a partir de um backup) no projeto atual. O método permite economizar tempo e espaço no disco rígido.

A situação: existe um banco de dados espesso (digamos, cem GB). Eu gostaria de ter esse banco de dados com todos os dados separadamente para cada desenvolvedor e não gastar um disco de terabyte nele. A seguir, uma solução para o MSSQL para Windows usando o PowerShell.

Me deparei com um utilitário Redgate SQL Clone. O site tem uma descrição de como funciona . O ponto principal é usar uma coisa dessas: diferenciar o disco rígido virtual. Isso se traduz em russo como um "disco virtual diferencial" - um disco no qual apenas a diferença em relação ao disco "pai" é armazenada.

Detalhes sob o corte

O esquema de trabalho é o seguinte:

  1. Criamos e conectamos o disco virtual usual (mais tarde ele se tornará o pai).
  2. Criamos uma instância de banco de dados a partir da qual os clones serão feitos. Limpamos os dados profissionais, preparamos o banco de dados completamente para trabalhar em um ambiente de teste. Colocamos os arquivos do banco de dados no disco virtual.
  3. Desconecte o banco de dados do servidor. Desconectamos o disco virtual.
  4. Crie um disco diferencial. Nós nos conectamos ao sistema. Conectamos o banco de dados desse disco ao servidor sql.
  5. Repita a etapa 4 até que um número harmonioso de bancos de dados seja alcançado.

A criação de um disco pai não será descrita, porque isso pode ser feito manualmente através da interface gráfica de gerenciamento de disco. Bem, ou comandos do Google e complemente os scripts fornecidos no artigo.

Nota vezes:

No Windows 10 e no Windows Server 2016, há o comando PowerShell New-VHD . Para quem usa versões anteriores do servidor, existe um utilitário diskpart. Automatizar o trabalho não é muito conveniente, porque na entrada, ele recebe um arquivo com comandos para executar.

Nota dois:

Porque Como os arquivos do banco de dados são colocados no disco diferencial, o desempenho dessa solução está longe de ser o mesmo. Acontece vários níveis de indireção: o registro vai para o banco de dados, que fica em um disco virtual que armazena a diferença na casa que Jack construiu. Não tenho números de desempenho específicos (porque, no nosso circuito de testes, essa não é a primeira pergunta). Ficaria grato se alguém medisse quanto a velocidade de gravação / leitura está drenando.

Nota três:

Porque Os scripts não foram projetados para uso amplo e são fornecidos apenas, por exemplo, com maior curvatura e forte vinculação ao MSSQL.

Vamos inicializar algumas variáveis:
$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; 


Porque o script é executado na máquina do desenvolvedor e todas as ações são executadas na máquina com o servidor sql, pressupõe-se que o sistema de comunicação remoto do PowerShell esteja configurado. Todos os comandos são executados em uma sessão aberta.

Get-StoredCredential é um comando para salvar credenciais na máquina local (instalada separadamente). Em princípio, você pode ficar sem ele, e é por isso que ele está envolvido em try / catch.

A seguir está o código de execução do script 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 } } 


Comandos sql que executo através do SQLCMD:
 function run_sql([string]$sql) { Write-Host $sql SQLCMD -S $server -d master -U $($db_cred.UserName) -P $($db_cred.Password) -Q $sql } 


Criando disco diferencial:
  run_script "create vdisk file=`"$root_path\$name.vhdx`" parent=`"$root_path\parent_disk.vhdx`"" 


Em seguida, conecte o disco e o banco de dados:
  $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" 


Esta parte da "função ls: [dz]: -n" é apenas um tipo de mágica para obter uma lista de letras de unidade. Como funciona - não faço ideia, copiado do stackoverflow.

No código acima, a maior dificuldade é obter o disco virtual resultante e colocá-lo em uma letra específica. Ele também precisa fazer online com antecedência.

Desconectar uma unidade é um pouco mais fácil:
  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 


Juntando tudo:
 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 


Vezes Achtung:

Se você reiniciar o servidor, será atormentado por explicar ao servidor sql que esses bancos de dados não existem e você precisará reconectá-los.

Alcançar dois:

O autor, é claro, verificou os comandos em seu circuito de teste, mas não pretende garantir nada (principalmente o desempenho). A seu próprio risco.

Total:

O lançamento de um banco de dados de teste adicional leva alguns minutos e 40 MB no disco. Portanto, é muito mais conveniente para cada desenvolvedor organizar sua própria instância de banco de dados.

Opcional:

O mesmo script pode ser usado para aumentar o banco de dados para testes de integração.

Espero que seja útil para alguém.

Source: https://habr.com/ru/post/pt440804/


All Articles