PowerShell을 사용하여 Azure SQL Database의 여러 데이터베이스 간에 데이터 동기화
적용 대상: Azure SQL Database
Important
SQL 데이터 동기화는 2027년 9월 30일에 종료됩니다. 대체 데이터 복제/동기화 솔루션으로 마이그레이션하는 것이 좋습니다.
이 Azure PowerShell 예제는 Azure SQL Database의 여러 데이터베이스 간에 데이터 동기화를 수행하도록 SQL 데이터 동기화를 구성합니다.
Azure를 구독하고 있지 않다면 시작하기 전에 Azure 체험 계정을 만듭니다.
참고
이 문서에서는 Azure와 상호 작용하는 데 권장되는 PowerShell 모듈인 Azure Az PowerShell 모듈을 사용합니다. Az PowerShell 모듈을 시작하려면 Azure PowerShell 설치를 참조하세요. Az PowerShell 모듈로 마이그레이션하는 방법에 대한 자세한 내용은 Azure PowerShell을 AzureRM에서 Azure로 마이그레이션을 참조하세요.
Azure Cloud Shell 사용
Azure는 브라우저를 통해 사용할 수 있는 대화형 셸 환경인 Azure Cloud Shell을 호스트합니다. Cloud Shell에서 Bash 또는 PowerShell을 사용하여 Azure 서비스 작업을 수행할 수 있습니다. 로컬 환경에 아무 것도 설치할 필요 없이 Azure Cloud Shell의 미리 설치된 명령을 사용하여 이 문서의 코드를 실행할 수 있습니다.
Azure Cloud Shell을 시작하려면 다음을 수행합니다.
옵션 | 예제/링크 |
---|---|
코드 블록의 오른쪽 위 모서리에서 사용을 선택합니다. 사용해보기를 선택하면 코드가 Cloud Shell에 자동으로 복사되지 않습니다. | |
https://shell.azure.com으로 이동하거나 Cloud Shell 시작 단추를 선택하여 브라우저에서 Cloud Shell을 엽니다. | |
Azure Portal의 오른쪽 위에 있는 메뉴 모음에서 Cloud Shell 단추를 선택합니다. |
이 문서의 코드를 Azure Cloud Shell에서 실행하려면 다음을 수행합니다.
Cloud Shell을 시작합니다.
코드 블록의 복사 단추를 선택하여 코드를 복사합니다.
Windows 및 Linux에서 Ctrl+Shift+V를 선택하거나 macOS에서 Cmd+Shift+V를 선택하여 코드를 Cloud Shell 세션에 붙여넣습니다.
Enter를 선택하여 코드를 실행합니다.
PowerShell을 로컬로 설치하고 사용하도록 선택하는 경우 이 자습서에는 Az PowerShell 1.4.0 이상이 필요합니다. 업그레이드해야 하는 경우 Azure PowerShell 모듈 설치를 참조하세요. 또한 PowerShell을 로컬로 실행하는 경우 Connect-AzAccount
를 실행하여 Azure와 연결해야 합니다.
SQL 데이터 동기화의 개요는 Azure의 SQL 데이터 동기화란?을 참조하세요.
SQL 데이터 동기화는 Azure SQL Managed Instance 및 Azure Synapse Analytics를 지원하지 않습니다.
필수 조건
- AdventureWorksLT 샘플 데이터베이스에서 Azure SQL 데이터베이스에 데이터베이스를 허브 데이터베이스로 만듭니다.
- 동일한 지역의 Azure SQL Database에 있는 데이터베이스를 동기화 데이터베이스로 만듭니다.
- 예제를 실행하기 전에 매개 변수 자리 표시자를 업데이트합니다.
예제
using namespace Microsoft.Azure.Commands.Sql.DataSync.Model
using namespace System.Collections.Generic
# hub database info
$subscriptionId = "<subscriptionId>"
$resourceGroupName = "<resourceGroupName>"
$serverName = "<serverName>"
$databaseName = "<databaseName>"
# sync database info
$syncDatabaseResourceGroupName = "<syncResourceGroupName>"
$syncDatabaseServerName = "<syncServerName>"
$syncDatabaseName = "<syncDatabaseName>"
# sync group info
$syncGroupName = "<syncGroupName>"
$conflictResolutionPolicy = "HubWin" # can be HubWin or MemberWin
$intervalInSeconds = 300 # sync interval in seconds (must be no less than 300)
# member database info
$syncMemberName = "<syncMemberName>"
$memberServerName = "<memberServerName>"
$memberDatabaseName = "<memberDatabaseName>"
$memberDatabaseType = "SqlServerDatabase" # can be AzureSqlDatabase or SqlServerDatabase
$syncDirection = "Bidirectional" # can be Bidirectional, Onewaymembertohub, Onewayhubtomember
# sync agent info
$syncAgentName = "<agentName>"
$syncAgentResourceGroupName = "<syncAgentResourceGroupName>"
$syncAgentServerName = "<syncAgentServerName>"
$syncMemberResourceId = "/subscriptions/$subscriptionId/resourceGroups/$resourceGroupName/providers/Microsoft.Sql/servers/$serverName/databases/$syncMemberDBName"
# temp file to save the sync schema
$tempFile = $env:TEMP+"\syncSchema.json"
# list of included columns and tables in quoted name
$includedColumnsAndTables = "[SalesLT].[Address].[AddressID]",
"[SalesLT].[Address].[AddressLine2]",
"[SalesLT].[Address].[rowguid]",
"[SalesLT].[Address].[PostalCode]",
"[SalesLT].[ProductDescription]"
$metadataList = [System.Collections.ArrayList]::new($includedColumnsAndTables)
Connect-AzAccount
Select-AzSubscription -SubscriptionId $subscriptionId
# use if it's safe to show password in script, otherwise use PromptForCredential
# $user = "username"
# $password = ConvertTo-SecureString -String "password" -AsPlainText -Force
# $credential = New-Object -TypeName "System.Management.Automation.PSCredential" -ArgumentList $user, $password
$credential = $Host.ui.PromptForCredential("Need credential",
"Please enter your user name and password for server "+$serverName+".database.windows.net",
"",
"")
# create a new sync group (if you use private link, make sure to manually approve it)
Write-Host "Creating Sync Group "$syncGroupName"..."
New-AzSqlSyncGroup -ResourceGroupName $resourceGroupName -ServerName $serverName -DatabaseName $databaseName -Name $syncGroupName `
-SyncDatabaseName $syncDatabaseName -SyncDatabaseServerName $syncDatabaseServerName -SyncDatabaseResourceGroupName $syncDatabaseResourceGroupName `
-ConflictResolutionPolicy $conflictResolutionPolicy -DatabaseCredential $credential -UsePrivateLinkConnection | Format-list
# use if it's safe to show password in script, otherwise use PromptForCredential
# $user = "username"
# $password = ConvertTo-SecureString -String "password" -AsPlainText -Force
# $credential = New-Object -TypeName "System.Management.Automation.PSCredential" -ArgumentList $user, $password
$credential = $Host.ui.PromptForCredential("Need credential",
"Please enter your user name and password for server "+$serverName+".database.windows.net",
"",
"")
# add a new sync member (if you use private link, make sure to manually approve it)
Write-Host "Adding member"$syncMemberName" to the sync group..."
New-AzSqlSyncMember -ResourceGroupName $resourceGroupName -ServerName $serverName -DatabaseName $databaseName `
-SyncGroupName $syncGroupName -Name $syncMemberName -MemberDatabaseType $memberDatabaseType -SyncAgentResourceGroupName $syncAgentResourceGroupName `
-SyncAgentServerName $syncAgentServerName -SyncAgentName $syncAgentName -SyncDirection $syncDirection -SqlServerDatabaseID $syncAgentInfo.DatabaseId `
-SyncMemberAzureDatabaseResourceId $syncMemberResourceId -UsePrivateLinkConnection | Format-list
# update existing sync member to use private link connection
Update-AzSqlSyncMember `
-ResourceGroupName $resourceGroupName -ServerName $serverName -DatabaseName $databaseName -SyncGroupName $syncGroupName -Name $syncMemberName `
-MemberDatabaseCredential $memberDatabaseCredential -SyncMemberAzureDatabaseResourceId $syncMemberResourceId -UsePrivateLinkConnection $true
# update existing sync group and remove private link connection
Update-AzSqlSyncGroup `
-ResourceGroupName $resourceGroupName -ServerName $serverName -DatabaseName $databaseName -Name $syncGroupName -UsePrivateLinkConnection $false
# run the following Get-AzSqlSyncGroup/ Get-AzSqlSyncMember commands to confirm that a private link has been setup for Data Sync, if you decide to use private link.
# Get-AzSqlSyncMember returns information about one or more Azure SQL Database Sync Members. Specify the name of a sync member to see information for only that sync member.
Get-AzSqlSyncMember `
-ResourceGroupName $resourceGroupName -ServerName $serverName -DatabaseName $databaseName -SyncGroupName $syncGroupName -Name $syncMemberName ` | Format-List
# Get-AzSqlSyncGroup returns information about one or more Azure SQL Database Sync Groups. Specify the name of a sync group to see information for only that sync group.
Get-AzSqlSyncGroup `
-ResourceGroupName $resourceGroupName -ServerName $serverName -DatabaseName $databaseName ` | Format-List
# approve private endpoint connection, if you decide to use private link
Approve-AzPrivateEndpointConnection `
-Name myPrivateEndpointConnection -ResourceGroupName myResourceGroup -ServiceName myPrivateLinkService
# refresh database schema from hub database, specify the -SyncMemberName parameter if you want to refresh schema from the member database
Write-Host "Refreshing database schema from hub database..."
$startTime = Get-Date
Update-AzSqlSyncSchema -ResourceGroupName $resourceGroupName -ServerName $serverName -DatabaseName $databaseName -SyncGroupName $syncGroupName
# waiting for successful refresh
$startTime = $startTime.ToUniversalTime()
$timer=0
$timeout=90
# check the log and see if refresh has gone through
Write-Host "Check for successful refresh..."
$isSucceeded = $false
while ($isSucceeded -eq $false) {
Start-Sleep -s 10
$timer=$timer+10
$details = Get-AzSqlSyncSchema -SyncGroupName $syncGroupName -ServerName $serverName -DatabaseName $databaseName -ResourceGroupName $resourceGroupName
if ($details.LastUpdateTime -gt $startTime) {
Write-Host "Refresh was successful"
$isSucceeded = $true
}
if ($timer -eq $timeout) {
Write-Host "Refresh timed out"
break;
}
}
# get the database schema
Write-Host "Adding tables and columns to the sync schema..."
$databaseSchema = Get-AzSqlSyncSchema -ResourceGroupName $ResourceGroupName -ServerName $ServerName `
-DatabaseName $DatabaseName -SyncGroupName $SyncGroupName `
$databaseSchema | ConvertTo-Json -depth 5 -Compress | Out-File "C:\Users\OnPremiseServer\AppData\Local\Temp\syncSchema.json"
$newSchema = [AzureSqlSyncGroupSchemaModel]::new()
$newSchema.Tables = [List[AzureSqlSyncGroupSchemaTableModel]]::new();
# add columns and tables to the sync schema
foreach ($tableSchema in $databaseSchema.Tables) {
$newTableSchema = [AzureSqlSyncGroupSchemaTableModel]::new()
$newTableSchema.QuotedName = $tableSchema.QuotedName
$newTableSchema.Columns = [List[AzureSqlSyncGroupSchemaColumnModel]]::new();
$addAllColumns = $false
if ($MetadataList.Contains($tableSchema.QuotedName)) {
if ($tableSchema.HasError) {
$fullTableName = $tableSchema.QuotedName
Write-Host "Can't add table $fullTableName to the sync schema" -foregroundcolor "Red"
Write-Host $tableSchema.ErrorId -foregroundcolor "Red"
continue;
}
else {
$addAllColumns = $true
}
}
foreach($columnSchema in $tableSchema.Columns) {
$fullColumnName = $tableSchema.QuotedName + "." + $columnSchema.QuotedName
if ($addAllColumns -or $MetadataList.Contains($fullColumnName)) {
if ((-not $addAllColumns) -and $tableSchema.HasError) {
Write-Host "Can't add column $fullColumnName to the sync schema" -foregroundcolor "Red"
Write-Host $tableSchema.ErrorId -foregroundcolor "Red"
}
elseif ((-not $addAllColumns) -and $columnSchema.HasError) {
Write-Host "Can't add column $fullColumnName to the sync schema" -foregroundcolor "Red"
Write-Host $columnSchema.ErrorId -foregroundcolor "Red"
}
else {
Write-Host "Adding"$fullColumnName" to the sync schema"
$newColumnSchema = [AzureSqlSyncGroupSchemaColumnModel]::new()
$newColumnSchema.QuotedName = $columnSchema.QuotedName
$newColumnSchema.DataSize = $columnSchema.DataSize
$newColumnSchema.DataType = $columnSchema.DataType
$newTableSchema.Columns.Add($newColumnSchema)
}
}
}
if ($newTableSchema.Columns.Count -gt 0) {
$newSchema.Tables.Add($newTableSchema)
}
}
# convert sync schema to JSON format
$schemaString = $newSchema | ConvertTo-Json -depth 5 -Compress
# work around a PowerShell bug
$schemaString = $schemaString.Replace('"Tables"', '"tables"').Replace('"Columns"', '"columns"').Replace('"QuotedName"', '"quotedName"').Replace('"MasterSyncMemberName"','"masterSyncMemberName"')
# save the sync schema to a temp file
$schemaString | Out-File $tempFile
# update sync schema
Write-Host "Updating the sync schema..."
Update-AzSqlSyncGroup -ResourceGroupName $resourceGroupName -ServerName $serverName `
-DatabaseName $databaseName -Name $syncGroupName -Schema $tempFile
$syncLogStartTime = Get-Date
# trigger sync manually
Write-Host "Trigger sync manually..."
Start-AzSqlSyncGroupSync -ResourceGroupName $resourceGroupName -ServerName $serverName -DatabaseName $databaseName -SyncGroupName $syncGroupName
# check the sync log and wait until the first sync succeeded
Write-Host "Check the sync log..."
$isSucceeded = $false
for ($i = 0; ($i -lt 300) -and (-not $isSucceeded); $i = $i + 10) {
Start-Sleep -s 10
$syncLogEndTime = Get-Date
$syncLogList = Get-AzSqlSyncGroupLog -ResourceGroupName $resourceGroupName -ServerName $serverName -DatabaseName $databaseName `
-SyncGroupName $syncGroupName -StartTime $syncLogStartTime.ToUniversalTime() -EndTime $syncLogEndTime.ToUniversalTime()
if ($synclogList.Length -gt 0) {
foreach ($syncLog in $syncLogList) {
if ($syncLog.Details.Contains("Sync completed successfully")) {
Write-Host $syncLog.TimeStamp : $syncLog.Details
$isSucceeded = $true
}
}
}
}
if ($isSucceeded) {
# enable scheduled sync
Write-Host "Enable the scheduled sync with 300 seconds interval..."
Update-AzSqlSyncGroup -ResourceGroupName $resourceGroupName -ServerName $serverName -DatabaseName $databaseName `
-Name $syncGroupName -IntervalInSeconds $intervalInSeconds
}
else {
# output all log if sync doesn't succeed in 300 seconds
$syncLogEndTime = Get-Date
$syncLogList = Get-AzSqlSyncGroupLog -ResourceGroupName $resourceGroupName -ServerName $serverName -DatabaseName $databaseName `
-SyncGroupName $syncGroupName -StartTime $syncLogStartTime.ToUniversalTime() -EndTime $syncLogEndTime.ToUniversalTime()
if ($synclogList.Length -gt 0) {
foreach ($syncLog in $syncLogList) {
Write-Host $syncLog.TimeStamp : $syncLog.Details
}
}
}
배포 정리
샘플 스크립트를 실행한 후에는 다음 명령을 실행하여 리소스 그룹 및 해당 그룹에 연결된 모든 리소스를 제거할 수 있습니다.
Remove-AzResourceGroup -ResourceGroupName $ResourceGroupName
Remove-AzResourceGroup -ResourceGroupName $SyncDatabaseResourceGroupName
스크립트 설명
이 스크립트는 다음 명령을 사용합니다. 표에 있는 각 명령은 명령에 해당하는 문서에 연결됩니다.
명령 | 주의 |
---|---|
New-AzSqlSyncAgent | 새 동기화 에이전트를 만듭니다. |
New-AzSqlSyncAgentKey | 동기화 에이전트와 연결된 에이전트 키를 생성합니다. |
Get-AzSqlSyncAgentLinkedDatabase | 동기화 에이전트에 대한 모든 정보를 가져옵니다. |
New-AzSqlSyncMember | 동기화 그룹에 새 구성원을 추가합니다. |
Update-AzSqlSyncSchema | 데이터베이스 스키마 정보를 새로 고칩니다. |
Get-AzSqlSyncSchema | 데이터베이스 스키마 정보를 가져옵니다. |
Update-AzSqlSyncGroup | 동기화 그룹을 업데이트합니다. |
Start-AzSqlSyncGroupSync | 동기화를 트리거합니다. |
Get-AzSqlSyncGroupLog | 동기화 로그를 확인합니다. |
관련 콘텐츠
Azure PowerShell에 대한 자세한 내용은 Azure PowerShell 설명서를 참조하세요.
추가 SQL Database PowerShell 스크립트 샘플은 Azure SQL 데이터베이스 PowerShell 스크립트에 있습니다.
SQL 데이터 동기화에 대한 자세한 내용은 다음을 참조하세요.
- 개요 - Azure의 SQL 데이터 동기화를 사용하여 여러 클라우드 및 온-프레미스 데이터베이스의 데이터 동기화
- 데이터 동기화 설정
- Azure Portal 사용 - 자습서: Azure SQL Database와 SQL Server 간에 데이터를 동기화하도록 SQL 데이터 동기화 설정
- PowerShell 사용 - PowerShell을 사용하여 Azure SQL 데이터베이스 및 SQL Server의 데이터베이스 간에 데이터 동기화
- Data Sync Agent - Azure의 SQL 데이터 동기화용 Data Sync Agent
- 모범 사례 - Azure의 SQL 데이터 동기화 모범 사례
- 모니터 - Azure Monitor 로그를 사용하여 SQL 데이터 동기화 모니터링
- 문제 해결 - Azure의 SQL 데이터 동기화 문제 해결
- 동기화 스키마 업데이트
- Transact-SQL 사용 - Azure의 SQL 데이터 동기화에서 스키마 변경 내용 복제 자동화
- PowerShell 사용 - PowerShell을 사용하여 기존 동기화 그룹의 동기화 스키마 업데이트
SQL Database에 대한 자세한 내용은 다음을 참조하세요.