Je décrirai brièvement comment le clonage de la base de données a été organisé (la création de plusieurs instances de base de données à partir d'une sauvegarde) sur le projet en cours. La méthode permet de gagner du temps et de l'espace sur le disque dur.
La situation: il existe une base de données épaisse (disons, une centaine de Go). Je voudrais avoir cette base de données avec toutes les données séparément pour chaque développeur et ne pas y consacrer un disque de téraoctets. Ce qui suit est une solution pour MSSQL pour Windows utilisant PowerShell.
Je suis tombé sur un utilitaire Redgate SQL Clone. Le site a une
description de son fonctionnement . L'essentiel est d'utiliser une telle chose: différencier le disque dur virtuel. Cela se traduit en russe par un «disque virtuel différentiel» - un disque sur lequel seule la différence par rapport au disque «parent» est stockée.
Détails sous la coupe
Le plan de travail est le suivant:
- Nous créons et connectons le disque virtuel habituel (il deviendra plus tard le parent).
- Nous créons une instance de base de données à partir de laquelle les clones seront créés. Nous nettoyons les pro-données, préparons complètement la base de données pour le travail dans un environnement de test. Nous plaçons les fichiers de base de données sur le disque virtuel.
- Déconnectez la base de données du serveur. Nous déconnectons le disque virtuel.
- Créez un disque de différenciation. Nous nous connectons au système. Nous connectons la base de données de ce disque au serveur SQL.
- Répétez l'étape 4 jusqu'à ce qu'un nombre harmonieux de bases de données soit atteint.
La création d'un disque parent ne sera pas décrite, car cela peut être fait manuellement via l'interface graphique de gestion des disques. Eh bien, ou les commandes google et complétez les scripts donnés dans l'article.
Notez les heures:
Dans Windows 10 et Windows Server 2016, il existe la commande PowerShell
New-VHD . Pour ceux qui utilisent les versions précédentes du serveur, il existe un utilitaire diskpart. Automatiser le travail avec n'est pas très pratique, car en entrée, il reçoit un fichier avec des commandes à exécuter.
Remarque deux:
Parce que Étant donné que les fichiers de base de données sont placés sur le disque de différenciation, les performances d'une telle solution sont loin d'être à la hauteur. Il se révèle plusieurs niveaux d'indirection: l'enregistrement va à la base de données, qui se trouve sur un disque virtuel qui stocke la différence dans la maison que Jack a construite. Je n'ai pas de chiffres de performances spécifiques (car sur notre circuit de test, ce n'est pas la première question de toute façon). Je serais reconnaissant si quelqu'un mesure combien la vitesse d'écriture / lecture s'épuise.
Remarque trois:
Parce que les scripts n'étaient pas destinés à une utilisation généralisée et sont fournis uniquement par exemple, il y a une courbure accrue et une liaison étroite à MSSQL.
Nous initialiserons quelques variables:$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;
Parce que le script est exécuté sur la machine du développeur, et toutes les actions sont effectuées sur la machine avec le serveur sql, il est supposé que la communication à distance powershell est configurée. Toutes les commandes sont exécutées dans une session ouverte.
Get-StoredCredential est une commande pour enregistrer les informations d'identification sur la machine locale (installée séparément). En principe, vous pouvez vous en passer, c'est pourquoi il est enveloppé dans try / catch.
Voici le code d'exécution du 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 } }
Commandes SQL que j'exécute via SQLCMD: function run_sql([string]$sql) { Write-Host $sql SQLCMD -S $server -d master -U $($db_cred.UserName) -P $($db_cred.Password) -Q $sql }
Création d'un disque de différenciation: run_script "create vdisk file=`"$root_path\$name.vhdx`" parent=`"$root_path\parent_disk.vhdx`""
Ensuite, connectez le disque et la base de données: $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"
Ce morceau de «fonction ls: [dz]: -n» est juste une sorte de magie pour obtenir une liste de lettres de lecteur. Comment ça marche - aucune idée, copié à partir de stackoverflow.
Dans le code ci-dessus, la plus grande difficulté est d'obtenir le disque virtuel résultant et de le mettre sur une lettre spécifique. Il doit également le faire en ligne au préalable.
Déconnecter un lecteur est un peu plus simple: 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
Mettre tout cela ensemble: 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
Temps Achtung:
Si vous redémarrez le serveur, vous êtes tourmenté d'expliquer au serveur SQL que ces bases de données n'existent pas et vous devez les reconnecter.
Achtung deux:
L'auteur, bien sûr, a vérifié les commandes sur son circuit de test, mais n'a pas l'intention de garantir quoi que ce soit (en particulier leurs performances). A vos risques et périls.
Total:
Le lancement d'une base de données de test supplémentaire prend quelques minutes et 40 Mo sur le disque. Ainsi, il est beaucoup plus pratique pour chaque développeur d'organiser sa propre instance de base de données.
Facultatif:
Le même script peut être utilisé pour augmenter la base de données pour les tests d'intégration.
J'espère que cela sera utile à quelqu'un.