百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分类 > 正文

探索与SQL Clone和SQL Change Automation的数据库持续集成

ztj100 2025-04-07 21:25 15 浏览 0 评论

当您作为应用程序开发团队的成员时,值得建立一个例程,该例程从源代码控制开始进行日常构建,然后供应数据库的测试和开发实例。一个主要的优点是可以尽早检查代码和数据库,并且您可以证明可以完全从代码构建工作数据库。这意味着管理层可以确信资产全部在源代码控制中。这也意味着测试可以尽早发现问题。

这需要自动化。最困难的部分是按照依赖关系的顺序组装单个对象脚本,或者在迁移方法的情况下,按照清单指示的顺序组装单个对象脚本。最繁琐的部分是将数据加载到数据库中。当然,耗时的部分是准备数据,但这超出了本文的范围。我们假设您有一个用于测试的标准数据集,并且所有服务器都将共享它。

我使用SQL Change Automation(SCA)来构建数据库。微软的SMO扮演次要的整合角色,我也使用它来创建数据库。我使用SQL Clone(SQL Provisioning的一个组件)将完成的数据库分发到需要最新版本的各种基于团队的服务器。将流程调整为符合您的要求后,您可以在每次有人提交数据库更改时运行该流程。

SQL克隆简介

第一次处理SQL Clone时,总是会有轻微的文化冲击,因此,我将作简要说明(有关更多信息,请参见Clone的工作原理)。SQL Clone使用x64 Windows中的虚拟磁盘服务来允许将相同的字节(“数据映像”)作为“克隆”数据库重复使用,并根据需要多次在SQL Server实例上使用。SQL Server完全不知道Windows操作系统正在从事任何“烟雾和镜像”活动。数据映像仅在网络上的单个位置保存一次,并且永不更改。每个克隆上进行的任何更改都存储在本地克隆服务器上的差异磁盘中。

设置克隆所需的时间很少,这是设置虚拟硬盘(.vhdx)并将其装入所需的时间。无论实际数据库的大小如何,从该映像创建的初始克隆仅需要大约40MB的磁盘空间。

您将需要一个中央服务器,即SQL Clone服务器,在其上安装SQL Clone Web应用程序,该应用程序必须在网络上对所有其他计算机可见。您的服务器不必是Windows域的一部分即可运行SQL Clone,但是如果您在混合开发环境中运行,最简单的方法是在与Clone相同的计算机上为映像提供共享目录。服务器。克隆可以在任何SQL Server上运行,在该SQL Server上,分配给克隆代理的用户可以通过网络访问该共享映像目录。

网络中任何SQL Server实例都可以用来承载克隆。为此,他们需要在其上安装SQL Clone代理,这些代理被分配了具有sysadmin权限的本地用户。

一旦数据库开始需要大量数据来进行开发和测试工作,那么用当前版本的数据库和数据来配置所有必需的数据库实例的麻烦就会变得一发不可收拾。如果可以确定克隆具有正确的数据,则可以通过模式同步来实现,但这是不现实的。任何测试运行都必然会更改数据。要进行一系列集成测试,您需要在每次测试后将数据库还原到已知状态。传统上,这是通过备份/还原或重复分离和附加来完成的。

取而代之的是,我将使用SQL Clone,这使得拆卸和恢复克隆非常迅速,容易,这是拆卸的一部分,并且可以在PowerShell的一行中轻松地自动完成此过程。

PowerShell自动化的CI

接下来我将介绍的PowerShell脚本将允许您使用源控制程序的最新版本的架构以及一个或多个测试或开发的数据版本来维护测试和开发所需的各种数据库。我提供了一个AdventureBuild zip文件,其中包含AdventureWorks数据库的源以及开发和测试所需的数据(作为bcp文件)。如果您从包含敏感数据或个人数据的数据集开始,则在配置开发和测试服务器之前,需要屏蔽数据。

我假设这种情况下的原始数据库是正在开发的数据库,并且其源保存在“源代码管理”中,因此我不备份现有克隆,也不在删除克隆之前保存更改,并且不检查是否任何人仍然都有与克隆一起运行的会话。

该脚本通过同步到使用SMO创建的空数据库来使用SCA进行构建(但是您也可以使用SQL Compare)。您需要在运行SQL Server版本(等于或低于您需要将克隆部署到的最旧版本)的实例上构建数据库。无法将克隆运行在比其父代更早的SQL Server版本上。

我们使用BCP将数据导入到空版本中,然后使用SQL Clone创建数据库映像并将克隆部署到一台或多台服务器上的“克隆”数据库列表中。将使用创建过程创建的数据库副本来创建克隆或刷新现有克隆。最后应该是所有具有相同元数据和数据的数据库。您需要安装最新版本的Clone PowerShell cmdlet(Clone应用程序中的Settings | PowerShell)。

如果您有SCA但没有SQL Clone,则仍然可以执行此操作,但是它会慢得多,并且会占用更多空间。

该解决方案省略了一些更详细的细节,您可以在其他文章中找到大多数细节。例如,我不会为数据库生成文档网站,也不会使用SQL Code Guard 对代码进行检查,也不会发送有关构建进度的信息。我没有显示如何将脚本和模板应用于每个单独的克隆以自定义安装或使数据库符合服务器的设置。

配置文件

为简单起见,我将所有设置放在单独的脚本中。其中包含要创建或更新的克隆列表,必须构建的数据库的详细信息,要在其中找到数据和源代码的位置以及克隆服务器和共享映像目录的位置。显然,您需要全部填写。

$Database = 'OurBuild'

$Repository= 'OurPathToTheScriptAndData'

@{

"Source" = @{

#specify the various directories you want in order to store files and logs

#The location of the executable SQL data insertion script.

'DataSyncPath' = "$Repository\$Database\data";

#where you want to put the reports for this particular database.

'ReportPath' = "$Repository\$Database";

#where you have (or want to put) the source of the current database.

'DatabasePath' = "$Repository\$Database\Build";

}

"Build" = @{

#now we'll specify where we want the new build. We will clone from this.

'NewBuildServer' = 'OurServer'; #The Server

'NewBuildInstance' = ''; #The SQL Server instance

'NewDatabase' = "New$Database"; #The name of the database

'username' = 'Phil Factor';#leave blank if windows authentication

'SQLCompareOptions' ='NoTransactions'# you sometimes need these for a complex build

}

"Image" = @{

#create an image of what we built

'Name' = "$($database)";

'ServerURL' = 'http://CloneServer:14145';

'ImageDirectoryURL'='\\CloneServer\Clone'

}

"Clones" = @(

@{ "NetName" = "AServer"; "Database" = "$($database)1" },

@{ "NetName" = "AnotherServer"; "Database" = "$($database)2" },

@{ "NetName" = "YetAnotherServer"; "Database" = "$($database)3" },

@{ "NetName" = "StillAnotherServer"; "Database" = "$($database)4" },

@{ "NetName" = "AndYetAnotherServer"; "Database" = "$($database)5" },

@{ "NetName" = "YeGodsAnotherServer"; "Database" = "$($database)6" }

)

}

该目录必须与以下PowerShell脚本位于同一目录中。

CI脚本

这是用于构建和填充数据库以及部署克隆的PowerShell脚本:

$VerbosePreference = "Continue"

#-- just to save typing ----

$MS = 'Microsoft.SQLServer'

$My = "$MS.Management.Smo"

$Mc = "$MS.Management.Common"

$popVerbosity = $VerbosePreference

$VerbosePreference = "Silentlycontinue"

# the import process is very noisy if you are in verbose mode

Import-Module sqlserver -DisableNameChecking #load the SQLPS functionality

$VerbosePreference = $popVerbosity

# set "Option Explicit" to catch subtle errors

set-psdebug -strict

$ErrorActionPreference = "stop"

<# just to make it easier to understand, the various parameter values are structured in a

hierarechy. We iterate over the clones when making or updating them #>

#First we read in the configuration from a file (do it so we can use the ISE as well)

$Data = &"$(If($psISE)

{Split-Path -Path $psISE.CurrentFile.FullPath}

Else {$global:PSScriptRoot})\MySCACloneConfig.ps1"

<# we read in the data as a structure.

Then we do some sanity checking to make sure that the data is reasonably viable.

We apply defaults if possible #>

$Errors = @()

# the fourth value means -1 provide a blank default, 0 = not a directory-must be there,

# 1=create if not exist, 2 = must already exist

@(($data.source.DatabasePath, 'source', 'DatabasePath', 2),

($data.source.DataSyncpath, 'source', 'DataSyncPath', 2),

($data.source.ReportPath, 'source', 'ReportPath', 2),

($data.Build.NewBuildServer, 'Build', 'NewBuildServer', 0),

($data.Build.NewBuildInstance, 'Build', 'NewBuildInstance', 0),

($data.Build.NewDatabase, 'Build', 'NewDatabase', 0),

($data.Build.username, 'Build', 'username', -1),

($data.Build.SQLCompareOptions, 'build', 'SQLCompareOptions', -1)

($data.Image.Name, 'Image', 'Name', 0),

($data.Image.ImageDirectoryURL, 'Image', 'ImageDirectoryURL', 0),

($data.Image.ServerURL, 'Image', 'ServerURL', 0)

) | foreach{

if ($_[0] -eq $null) #if the parameter has'nt been provided

{# we give a default '' else flag up an error

if ($_[3] -eq -1) { $data.$_[1].$_[2] = '' }

else

{ $Errors += "There is no $($_[1]).$($_[2]) defined" }

}

elseif ($_[3] -ge 1) #it is a directory that needs to be tested

{

if (-not (Test-Path -PathType Container $_[0]))

{

if ($_[3] -eq 2)

{

New-Item -ItemType Directory -Force -Path $_[0] `

-ErrorAction silentlycontinue -ErrorVariable +Errors;

}

else { $Errors += "the path '$($_[0])'in $($_[1]).$($_[2]) does not exist" }

}

}

}

$TheLogfile="$($data.source.ReportPath)\ReportFile.txt"

if ($data.build.NewBuildInstance -eq '')

{$NewBuildServerInstance= "$($data.build.NewBuildServer)"}

else

{$NewBuildServerInstance= "$($data.build.NewBuildServer)\$($data.build.NewBuildInstance)"}

if ($NewBuildServerInstance -eq '') {$errors += 'No build server specified'}

if ($errors.Count -eq 0) #any errors are displayed at the end

{

#first make sure we can connect

$conn = new-object "$Mc.ServerConnection" $NewBuildServerInstance `

-ErrorAction silentlycontinue -ErrorVariable +Errors;

if ($data.build.username -ieq '')

{

# Crikey, this is easy, windows Passwords. Dont you love 'em?

$conn.LoginSecure = $true;

}

else

{

<# This is more elaborate a process than you might expect because we can't assume

that we can use Windows authentication, because of Azure, remote servers outside

the domain, and other such complications. We can't ever keep passwords for SQL

Server authentication as part of the static script data. At this stage, we ask

for passwords if they aren't known, and otherwise store them as secure strings

on file in the user area, protected by the workstation security.

#>

#create a connection object to manage credentials

$conn = new-object "$Mc.ServerConnection"

$conn.ServerInstance = $NewBuildServerInstance

$encryptedPasswordFile = `

"$env:USERPROFILE\$($data.build.Username)-$($conn.ServerInstance).txt"

# test to see if we know about the password in a secure string stored in the user area

if (Test-Path -path $encryptedPasswordFile -PathType leaf)

{

#has already got this set for this login so fetch it

$encrypted = Get-Content $encryptedPasswordFile | ConvertTo-SecureString

$Credentials = `

New-Object System.Management.Automation.PsCredential($data.build.Username, $encrypted)

}

else #then we have to ask the user for it

{

#hasn't got this set for this login

$Credentials = get-credential -Credential $data.build.username

$Credentials.Password | ConvertFrom-SecureString |

Set-Content $encryptedPasswordFile

}

$conn.LoginSecure = $false;

$conn.Login = $Credentials.UserName;

$conn.SecurePassword = $Credentials.Password;

}

$data.build.ServerConnection = $conn; #this sets our server connection for the build database

}

<# now that weve established all the connections we need we now build the database>

if ($errors.Count -eq 0)

{

#if the database already exists, then kill it

$TheBuildDatabase = $data.build.NewDatabase

$BuildServer = new-object ("$My.Server") $data.build.ServerConnection

if ($BuildServer.Databases[$TheBuildDatabase] -ne $null)

{ $BuildServer.KillDatabase($TheBuildDatabase) }

$BuildDatabase = `

New-Object ("$My.Database") ($BuildServer, $TheBuildDatabase)

$BuildDatabase.Create()

if ($BuildDatabase.name -ne $TheBuildDatabase)

{

$Errors += "Can't create the database '$($TheBuildDatabase)' in '$($data.build.ServerInstance)"

};

if ($data.build.ServerConnection.LoginSecure)

{

$data.build.Connection = New-DatabaseConnection `

-ServerInstance $NewBuildServerInstance `

-Database $TheBuildDatabase `

-ErrorAction silentlycontinue -ErrorVariable +Errors;

}

else

{

$data.build.Connection = New-DatabaseConnection `

-ServerInstance $NewBuildServerInstance `

-Database $TheBuildDatabase `

-Username $data.build.ServerConnection.Login `

-Password $data.build.ServerConnection.Password `

-ErrorAction silentlycontinue `

-ErrorVariable +Errors;

}

$syncResult = Sync-DatabaseSchema `

-Source $data.source.DatabasePath `

-Target $data.build.Connection `

-AbortOnWarningLevel None `

-SQLCompareOptions $data.build.SQLCompareOptions `

-ErrorAction silentlycontinue `

-ErrorVariable +Errors

}

if ($errors.Count -eq 0) # we can put the data in the database

{

<# Now we BCP all the table data in. As we are using native mode the utility

disables constraints for the table before doing the import #>

$BuildDatabase.Tables | #for every table

foreach {

$filename = "$($_.schema)_$($_.Name)" -replace '[\\\/\:\.]', '-';

$TheScriptPath = "$($data.source. DataSyncPath)\$($filename).bcp";

if (-not ($data.build.ServerConnection.LoginSecure))

{

$whatHappened = "`"$TheBuildDatabase`".`"$($_.Schema)`".`"$($_.Name)`""

$WhatHappened +=

BCP "`"$TheBuildDatabase`".`"$($_.Schema)`".`"$($_.Name)`"" `

in "`"$TheScriptPath`"" -q -n -N -E "-U$($data.build.ServerConnection.Login)" `

"-P$($data.build.ServerConnection.Password)" "-S$($data.build.ServerConnection.ServerInstance)";

}

else

{

$WhatHappened =

BCP "`"$TheBuildDatabase`".`"$($_.Schema)`".`"$($_.Name)`"" `

in "`"$TheScriptPath`"" -q -N -T -E "-S$($data.build.ServerConnection.ServerInstance)";

}

if ($WhatHappened -like '*Error *')

{ throw ("$whatHappened adding data to $TheBuildDatabase.$filename on $TheScriptPath") };

}

$result = $BuildServer.ConnectionContext.ExecuteNonQuery(

"EXEC sp_msforeachtable 'ALTER TABLE ? WITH CHECK CHECK CONSTRAINT all'");

"$($data.build.ServerConnection.ServerInstance) $TheBuildDatabase has been stocked with data"

}

<# we get through to clone>

if ($Errors.Count -eq 0)

{

#initialise SQL Clone by attempting to initate a connection with a SQL Clone Server.

Connect-SqlClone -ServerUrl $data.image.ServerURL -ErrorAction SilentlyContinue `

-ErrorVariable +Errors

}

<# now we slaughter all existing versions of the database. its so great that youve put everything worthwhile in source control and have sql prompt tab magic as well.>

if ($Errors.Count -eq 0)

{

$image = Get-SqlCloneImage `

-Name $data.image.Name -ErrorAction SilentlyContinue

if ($image -ne $null) #if it already exists

{

$clones = Get-SqlClone `

-Image $image -ErrorAction SilentlyContinue

if (!($clones -eq $null)) #delete all existing clones (normally, you'd save changes

{

$clones | Remove-SqlClone | Wait-SqlCloneOperation

}

Remove-SqlCloneImage -Image $image | Wait-SqlCloneOperation

}

}

if ($Errors.Count -eq 0)

{

Write-Verbose " Creating new image of $NewBuildServerInstance.$($data.build.NewDatabase)"

#create an image of what we built. We name it whatever we have specified

$SqlServerInstance = [RedGate.SqlClone.Client.Api.Objects.SqlServerInstanceResource](Get-SqlCloneSqlServerInstance |

Where-Object{ ($_.Serveraddress -ieq $data.build.NewBuildServer.Trim()) -and ($_.Instance.Trim() -ieq $data.build.NewBuildInstance.Trim())})

$ImageFileDestination = Get-SqlCloneImageLocation `

-Path $data.Image.ImageDirectoryURL

New-SqlCloneImage -Name $data.image.Name `

-SqlServerInstance $SqlServerInstance `

-DatabaseName $data.build.NewDatabase `

-Destination $ImageFileDestination `

-ErrorAction silentlycontinue `

-ErrorVariable +Errors | Wait-SqlCloneOperation

$ourCloneImage = Get-SqlCloneImage `

-Name $data.image.Name `

-ErrorAction SilentlyContinue

if ($ourCloneImage -eq $null)

{

$Errors += "couldn't find the clone $($data.image.Name) That has just been created"

}

if ($ourCloneImage.State -ne 'Created')

{$Errors += "We hit a problem with the image. It's state is $($ourCloneImage.State)"}

}

#clone it as whatever database is specified to whatever SQL Clone servers are specified

$data.clones | foreach {"$($_.Netname,$_.Database ) is ok"}

if ($Errors.Count -eq 0)

{

$data.clones | foreach {

$clone = $null; $Thedatabase = $_.Database;

#get the correct instance that has an agent installed on it.

$sqlServerInstance = (Get-SqlCloneSqlServerInstance | Where server -ieq $_.NetName);

if ($sqlServerInstance -eq $null) { Throw "Unable to find the clone agent for $($_.NetName)" }

write-verbose "Cloning $($_.Database) on $($_.NetName)"

$clone = Get-SqlClone `

-ErrorAction silentlyContinue `

-Name "$($TheDatabase)" `

-Location $sqlServerInstance

if (($clone) -ne $null)

{

write-warning "Removing Clone $Thedatabase that already existed on $($_.NetName)"

Remove-SqlClone $clone | Wait-SqlCloneOperation

}

Get-SqlCloneImage -Name $data.Image.Name |

New-SqlClone -Name "$($Thedatabase)" -Location $SqlServerInstance |

Wait-SqlCloneOperation

if ($errors.Count -gt 0)

{

break

}

}

}

if ($errors.Count -gt 0)

{

$errors| foreach{Write-error $_; "$((Get-Date).ToString()): $($_) the build process was aborted">>$TheLogFile;};

write-error("$($_)")

}

else

{

"$((Get-Date).ToString()): the build process had no errors">>$TheLogFile

}

结论

使用SQL Server数据库执行自动CI的方法有很多。考虑到丰富的团队方法,这也是一样。对我来说,SQL Clone的最大优势不仅在于自动化的简化和磁盘空间的节省,还在于抛弃正在测试的数据库并将其还原为构建版本的便捷性。数据库的大小越大,能够执行此操作的优势就越大。

我在这里显示的这个脚本是为了说明可能性。您可以添加许多内容,例如数据生成,脚本检查和数据屏蔽,但是SQL Clone的基本功能无疑为为更大的团队和数据库提供持续集成提供了很大的帮助。

相关推荐

Win10预览版10532已知问题汇总(微软win11正式版已知问题一览)

IT之家讯微软已向Insider用户推送了Win10预览版10532更新,本次更新对右键菜单、《Windows反馈》应用以及Edge浏览器进行了改进。除此之外还包含一些Bug,汇总如下,有意升级Wi...

Gabe Aul正测试Win10 Mobile 10532,Insider用户还需等

IT之家讯本月中旬微软向Insider用户推送了Win10Mobile预览版10512,该版本修复了一些Bug,增强了系统稳定性,但依然存在一些问题。今天,微软Insider项目负责人GabeAu...

微软开始推送Win10预览版10532快速版更新

8月28日消息,刚才,微软推送了Win10Build10532快速版,修复了之前的Bug,并带来了三项改进。主要来说,这次的更新改进了右键菜单的UI,使其更具Modern风格(见上图)。此外,更新...

Win10预览版10532更新内容大全(windows10更新预览版)

IT之家讯今天凌晨微软向Insider用户推送了Win10预览版10532快速版更新,本次更新主要带来了三处改进,汇总如下:o改进右键菜单,外观更加Modern。这是基于网友要求界面一致的反馈做出...

无法升级Win10预览版10532?也许Hyper-V在搞鬼

根据IT之家网友的反映,安装了微软虚拟机Hyper-V的Win10预览版用户无法成功升级Build10532版本,安装过程中会被要求回滚系统。很多朋友在尝试关闭虚拟机之后重启安装程序,结果仍然无法顺...

Win10预览版10532界面兴起“酷黑”风潮

Win10预览版10532的界面改动还是较为明显的,主要体现在右键菜单上面。总体来看,该版本的右键菜单间距更宽,视觉上更大气,操作上更便于触控。具体来说,任务栏右键菜单的变化最为明显。除了增加选项的宽...

Win10预览版10532上手图集(windows10预览版下载)

IT之家讯8月28日,微软今天推送了Win10预览版10532快速版更新,在该版本中,微软主要是加强细节上调整,并且主要是增强Edge浏览器性能等。在Windows10预览版10532中,微软改进了...

Win10预览版10532上手视频亮点演示

IT之家讯8月28日消息,今天凌晨微软向WindowsInsider快速通道用户推送了Win10预览版10532。在Windows10预览版10532中,微软改进了右键菜单,外观更加现代化。另外还...

第二篇 前端框架Vue.js(vue前端框架技术)

前端三大核心是网页开发的基础,Vue则是基于它们构建的“生产力工具”。通俗理解就是HTML是化妆的工具如眉笔,CSS是化妆品如口红,JavaScript是化妆后的互动,而Vue就是化妆助手。有了化妆工...

基于SpringBoot + vue2实现的旅游推荐管理系统

项目描述...

基于Vue以及iView组件的后端管理UI模板——iview-admin

介绍iView-admin是一套后端管理界面模板,基于Vue2.0,iView(现在为ViewUI)组件是一套完整的基于Vue的高质量组件库,虽然Github上有一套非常火的基于ElementUI...

别再说你会SPA开发了,这5个核心你真的搞懂了吗?

前言此spa非彼spa,不是你所熟知的spa。你所熟知的spa作者肯定是没有你熟悉的。我们这里指的是在前端开发中的一种模型,叫作单页应用程序,顾名思义,就是整个项目只有一个页面,而页面中的内容是动态的...

React.js Top20面试题(react.js中文官网)

概述作为React开发者,对框架的关键概念和原则有扎实的理解是很重要的。考虑到这一点,我整理了一份包含20个重要问题的清单,每个React开发者都应该知道,无论他们是在面试工作还是只是想提高技能。...

美媒:特朗普签署行政令后,FBI又发现约2400份、总计超14000页涉肯尼迪遇刺案文件

来源:环球时报新媒体1月23日特朗普下令公布肯尼迪遇刺案相关机密文件图源:美媒综合福克斯新闻网和Axios网站10日报道,在总统特朗普签署行政令,要求公布“肯尼迪遇刺案”相关政府机密文件之后,美国...

2021 年 Node.js 开发人员学习路线图

Node.js自发布以来,已成为业界重要破局者之一。Uber、Medium、PayPal和沃尔玛等大型企业,纷纷将技术栈转向Node.js。Node.js支持开发功能强大的应用,例如实时追踪...

取消回复欢迎 发表评论: