Ich werde kurz beschreiben, wie das Klonen der Datenbank (das Erstellen mehrerer Datenbankinstanzen aus einer Sicherung) im aktuellen Projekt organisiert wurde. Die Methode ermöglicht es, Zeit und Festplattenspeicher zu sparen.
Die Situation: Es gibt eine dicke Datenbank (sagen wir hundert GB). Ich möchte diese Datenbank mit allen Daten für jeden Entwickler separat haben und keine Terabyte-Festplatte dafür ausgeben. Das Folgende ist eine Lösung für MSSQL für Windows mit Powershell.
Ich bin auf ein Redgate SQL Clone-Dienstprogramm gestoßen. Die Site enthält eine
Beschreibung der Funktionsweise . Die Quintessenz ist, so etwas zu verwenden: Differenzierung der virtuellen Festplatte. Dies bedeutet Russisch als "differenzielle virtuelle Festplatte" - eine Festplatte, auf der nur der Unterschied zur "übergeordneten" Festplatte gespeichert ist.
Details unter dem Schnitt
Das Arbeitsschema ist wie folgt:
- Wir erstellen und stecken die übliche virtuelle Festplatte ein (sie wird später die übergeordnete Festplatte).
- Wir erstellen eine Datenbankinstanz, aus der Klone erstellt werden. Wir bereinigen die Pro-Daten und bereiten die Datenbank vollständig für die Arbeit in einer Testumgebung vor. Wir platzieren die Datenbankdateien auf der virtuellen Festplatte.
- Trennen Sie die Datenbank vom Server. Wir trennen die virtuelle Festplatte.
- Erstellen Sie eine differenzierende Festplatte. Wir verbinden uns mit dem System. Wir verbinden die Datenbank von dieser Festplatte mit dem SQL Server.
- Wiederholen Sie Schritt 4, bis eine harmonische Anzahl von Datenbanken erreicht ist.
Das Erstellen einer übergeordneten Festplatte wird nicht beschrieben, da Dies kann manuell über die grafische Oberfläche der Datenträgerverwaltung erfolgen. Nun, oder google Befehle und ergänzen Sie die im Artikel angegebenen Skripte.
Zeiten beachten:
In Windows 10 und Windows Server 2016 gibt es das Powershell-Commandlet
New-VHD . Für diejenigen, die frühere Versionen des Servers verwenden, gibt es ein Dienstprogramm für Diskparts. Die Arbeit damit zu automatisieren ist nicht sehr bequem, weil Am Eingang erhält es eine Datei mit auszuführenden Befehlen.
Anmerkung zwei:
Weil Da die Datenbankdateien auf der differenzierenden Festplatte abgelegt werden, ist die Leistung einer solchen Lösung bei weitem nicht gleich hoch. Es stellt sich heraus, dass mehrere Indirektionsebenen vorhanden sind: Der Datensatz geht in die Datenbank, die sich auf einer virtuellen Festplatte befindet, auf der der Unterschied in dem von Jack gebauten Haus gespeichert ist. Ich habe keine spezifischen Leistungszahlen (da dies auf unserer Teststrecke sowieso nicht die erste Frage ist). Ich wäre dankbar, wenn jemand messen würde, wie stark die Schreib- / Lesegeschwindigkeit abnimmt.
Anmerkung drei:
Weil Skripte waren nicht für eine weit verbreitete Verwendung gedacht und werden nur zum Beispiel bereitgestellt, da eine erhöhte Krümmung und eine enge Bindung an MSSQL bestehen.
Wir werden einige Variablen initialisieren:$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;
Weil Das Skript wird auf dem Computer des Entwicklers ausgeführt, und alle Aktionen werden auf dem Computer mit dem SQL Server ausgeführt. Es wird davon ausgegangen, dass Powershell-Remoting konfiguriert ist. Alle Befehle werden in einer offenen Sitzung ausgeführt.
Get-StoredCredential ist ein Commandlet zum Speichern von Anmeldeinformationen auf dem lokalen Computer (separat installiert). Im Prinzip können Sie darauf verzichten, weshalb es in try / catch verpackt ist.
Das Folgende ist der Ausführungscode für das Diskpart-Skript: 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 } }
SQL-Befehle, die ich über SQLCMD ausführe: function run_sql([string]$sql) { Write-Host $sql SQLCMD -S $server -d master -U $($db_cred.UserName) -P $($db_cred.Password) -Q $sql }
Erstellen einer differenzierenden Festplatte: run_script "create vdisk file=`"$root_path\$name.vhdx`" parent=`"$root_path\parent_disk.vhdx`""
Verbinden Sie als Nächstes die Festplatte und die Datenbank: $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"
Dieses Stück "ls function: [dz]: -n" ist nur eine Art Magie, um eine Liste von Laufwerksbuchstaben zu erhalten. Wie es funktioniert - keine Ahnung, aus dem Stackoverflow kopiert.
Im obigen Code besteht die größte Schwierigkeit darin, die resultierende virtuelle Festplatte abzurufen und auf einen bestimmten Buchstaben zu setzen. Er muss auch vorher online machen.
Das Trennen eines Laufwerks ist etwas einfacher: 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
Alles zusammen: 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
Achtung mal:
Wenn Sie den Server neu starten, werden Sie gequält, um dem SQL Server zu erklären, dass diese Datenbanken nicht vorhanden sind, und Sie müssen sie erneut verbinden.
Achtung zwei:
Der Autor hat natürlich die Befehle auf seiner Testschaltung überprüft, beabsichtigt jedoch nicht, etwas zu garantieren (insbesondere deren Leistung). Auf eigenes Risiko.
Gesamt:
Das Starten einer zusätzlichen Testdatenbank dauert einige Minuten und 40 MB auf der Festplatte. Daher ist es für jeden Entwickler viel bequemer, seine eigene Datenbankinstanz zu organisieren.
Optional:
Das gleiche Skript kann verwendet werden, um die Datenbank für Integrationstests zu erhöhen.
Ich hoffe, es wird jemandem nützlich sein.