Describiré brevemente cómo se organizó la clonación de la base de datos (la creación de varias instancias de base de datos a partir de una copia de seguridad) en el proyecto actual. El método permite ahorrar tiempo y espacio en el disco duro.
La situación: hay una base de datos gruesa (por ejemplo, cien GB). Me gustaría tener esta base de datos con todos los datos por separado para cada desarrollador y no gastar un disco de terabytes en ella. La siguiente es una solución para MSSQL para Windows usando Powershell.
Encontré una utilidad Redgate SQL Clone. El sitio tiene una
descripción de cómo funciona . La conclusión es usar tal cosa: diferenciar el disco duro virtual. Esto se traduce al ruso como un "disco virtual diferencial", un disco en el que solo se almacena la diferencia con respecto al disco "principal".
Detalles debajo del corte
El esquema de trabajo es el siguiente:
- Creamos y conectamos el disco virtual habitual (más tarde se convertirá en el padre).
- Creamos una instancia de base de datos a partir de la cual se realizarán los clones. Limpiamos los datos pro, preparamos la base de datos por completo para trabajar en un entorno de prueba. Colocamos los archivos de la base de datos en el disco virtual.
- Desconecte la base de datos del servidor. Desconectamos el disco virtual.
- Crea un disco de diferenciación. Nos conectamos al sistema. Conectamos la base de datos de este disco al servidor sql.
- Repita el paso 4 hasta lograr un número armonioso de bases de datos.
No se describirá la creación de un disco principal, porque Esto se puede hacer manualmente a través de la interfaz gráfica de administración de discos. Bueno, o google manda y complementa los scripts dados en el artículo.
Tiempos de nota:
En Windows 10 y Windows Server 2016 hay un comando PowerShell
New-VHD . Para aquellos que usan versiones anteriores del servidor, existe una utilidad diskpart. Automatizar el trabajo con él no es muy conveniente, porque en la entrada, recibe un archivo con comandos para ejecutar.
Nota dos:
Porque Dado que los archivos de la base de datos se colocan en el disco de diferenciación, el rendimiento de dicha solución está lejos de ser excelente. Resulta varios niveles de indirección: el registro va a la base de datos, que se encuentra en un disco virtual que almacena la diferencia en la casa que construyó Jack. No tengo números de rendimiento específicos (porque en nuestro circuito de prueba esta no es la primera pregunta de todos modos). Estaría agradecido si alguien mide cuánto está drenando la velocidad de escritura / lectura.
Nota tres:
Porque los scripts no estaban destinados a un uso generalizado y se proporcionan únicamente, por ejemplo, hay una mayor curvatura y una estrecha unión a MSSQL.
Inicializaremos algunas 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;
Porque el script se ejecuta en la máquina del desarrollador, y todas las acciones se realizan en la máquina con el servidor sql, se supone que la comunicación remota de PowerShell está configurada. Todos los comandos se ejecutan en una sesión abierta.
Get-StoredCredential es un comando para guardar la credencial en la máquina local (instalada por separado). En principio, puede prescindir de él, por lo que está envuelto en try / catch.
El siguiente es el código de ejecución del 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 ejecuto a través de SQLCMD: function run_sql([string]$sql) { Write-Host $sql SQLCMD -S $server -d master -U $($db_cred.UserName) -P $($db_cred.Password) -Q $sql }
Crear disco de diferenciación: run_script "create vdisk file=`"$root_path\$name.vhdx`" parent=`"$root_path\parent_disk.vhdx`""
A continuación, conecte el disco y la base de datos: $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 pieza de "ls function: [dz]: -n" es solo una especie de magia para obtener una lista de letras de unidad. Cómo funciona: ni idea, copiado de stackoverflow.
En el código anterior, la mayor dificultad es obtener el disco virtual resultante y ponerlo en una letra específica. También necesita hacer en línea de antemano.
Desconectar una unidad es un poco más 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
Poniendo todo junto: 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
Tiempos de Achtung:
Si reinicia el servidor, entonces está atormentado para explicarle al servidor SQL que estas bases de datos no existen y que necesita volver a conectarlas.
Achtung dos:
El autor, por supuesto, verificó los comandos en su circuito de prueba, pero no tiene la intención de garantizar nada (especialmente su rendimiento). A su propio riesgo.
Total:
El lanzamiento de una base de datos de prueba adicional lleva un par de minutos y 40 MB en disco. Por lo tanto, es mucho más conveniente para cada desarrollador organizar su propia instancia de base de datos.
Opcional:
Se puede usar el mismo script para generar la base de datos para las pruebas de integración.
Espero que sea útil para alguien.