使用即時快照備份 SQL Server

如果 SQL Server 資料庫檔案分散在多個磁碟上,您可以同時備份所有磁碟,維持應用程式和資料的一致性。您可以使用 Compute Engine 即時快照一致性群組,達成這個精確的時間點備份,備份一組磁碟中的資料

本教學課程說明如何使用 Compute Engine 快照,以及 SQL Server 2022 以上版本提供的 Transact-SQL (T-SQL) 快照功能,備份 SQL Server 資料庫。這項解決方案支援 Windows 和 Linux 部署作業,並盡量減少對即時工作負載的效能影響。

運作方式

工作流程包含下列主要步驟,這些步驟由在運算執行個體上執行的指令碼管理:

  1. 凍結資料庫:指令碼會將 T-SQL 指令傳送至 SQL Server,暫停目標資料庫的所有寫入作業。確保資料庫檔案處於一致狀態,方便備份。
  2. 建立即時快照:在資料庫凍結期間,為資料庫資料和記錄檔所在的磁碟建立一組即時快照。這相當於使用硬體或服務層級的快照機制。 Google Cloud
  3. 記錄及解除凍結:指令碼會將另一個 T-SQL 指令傳送至 SQL Server,以記錄備份中繼資料。這個指令會建立小型備份檔案,指向即時快照的一致性群組,並記錄在msdb備份記錄中。完成後,SQL Server 會自動解除凍結資料庫,並恢復正常運作。

整個過程通常會在不到一秒內完成,盡量縮短資料庫寫入凍結的時間。凍結期間,系統可以讀取資料,但無法寫入。您可以為資料庫設定 SUSPEND_FOR_SNAPSHOT_BACKUP=OFF,手動取消凍結狀態。

目標

在本教學課程中,您將瞭解如何完成下列工作:

  • 建立具備兩個資料磁碟的 SQL Server 執行個體。
  • 使用不同磁碟上的資料和記錄檔建立新資料庫。
  • 為執行 SQL Server 的 VM 的所有磁碟建立一致性群組。
  • 建立磁碟群組的即時快照。
  • 從即時快照建立新磁碟。

費用

在本文件中,您會使用下列 Google Cloud的計費元件:

如要根據預測用量估算費用,請使用 Pricing Calculator

初次使用 Google Cloud 的使用者可能符合免費試用期資格。

完成本文所述工作後,您可以刪除建立的資源,避免繼續計費,詳情請參閱「清除所用資源」。

事前準備

  1. 本教學課程需要 Google Cloud 專案。您可以建立新專案,或是選取現有專案:

    1. 在 Google Cloud 控制台的專案選擇器頁面中,選取或建立 Google Cloud 專案。

      選取或建立專案所需的角色

      • 選取專案:選取專案時,不需要具備特定 IAM 角色,只要您已獲授角色,即可選取任何專案。
      • 建立專案:如要建立專案,您需要「專案建立者」角色 (roles/resourcemanager.projectCreator),其中包含 resourcemanager.projects.create 權限。瞭解如何授予角色

      前往專案選取器

    2. 確認專案已啟用計費功能 Google Cloud

    3. 在 Google Cloud 控制台中啟用 Cloud Shell。

      啟用 Cloud Shell

  2. 確認已安裝並執行 Microsoft SQL Server 2022 以上版本。

所需權限

除了標準讀取權限外,請確保 SQL Server 管理員授予您目標資料庫的 ALTER DATABASE 權限。

如要取得建立執行個體和快照所需的權限,請要求管理員授予您專案的下列 IAM 角色:

  • 管理執行個體: compute.instanceAdmin.v1
  • 建立快照: compute.storageAdmin

如要進一步瞭解如何授予角色,請參閱「管理專案、資料夾和組織的存取權」。

您或許也能透過自訂角色或其他預先定義的角色,取得必要權限。

建立 Compute Engine SQL 執行個體

建立 SQL Server 執行個體。

  1. 在 Google Cloud 控制台中,按一下「啟用 Cloud Shell」啟動 Cloud Shell。按鈕,開啟 Cloud Shell

    前往 Google Cloud 控制台

  2. 建立 SQL Server 執行個體。貼上下列指令:

  REGION=REGION
  ZONE=$REGION-a
  VM_NAME=VM_NAME
  gcloud compute instances create $VM_NAME \
    --boot-disk-auto-delete \
    --boot-disk-size 100 \
    --boot-disk-type hyperdisk-balanced \
    --image-family sql-std-2022-win-2025 \
    --image-project windows-sql-cloud \
    --machine-type c4-highmem-4 \
    --zone $ZONE \
    --network-interface subnet=default \
    --tags sql-server-instant-snapshot \
    --scopes=cloud-platform,service-control,service-management,monitoring-write,logging-write,storage-rw \
    --create-disk=device-name=$VM_NAME-data-disk1,mode=rw,name=$VM_NAME-data-disk1,size=100,type=hyperdisk-balanced \
    --create-disk=device-name=$VM_NAME-data-disk2,mode=rw,name=$VM_NAME-data-disk2,size=100,type=hyperdisk-balanced

更改下列內容:

  • 區域:新執行個體的部署區域。
  • VM_NAME:新 SQL Server 執行個體的名稱。

建立磁碟一致性群組

  1. 建立一致性群組。

    gcloud compute resource-policies create disk-consistency-group $VM_NAME-snap-grp \
        --region=$REGION
    
  2. 將 VM 的磁碟新增至一致性群組。

    gcloud compute disks add-resource-policies $VM_NAME \
        --zone=$ZONE \
        --resource-policies=$VM_NAME-snap-grp
    
    gcloud compute disks add-resource-policies $VM_NAME-data-disk1 \
        --zone=$ZONE \
        --resource-policies=$VM_NAME-snap-grp
    
    gcloud compute disks add-resource-policies $VM_NAME-data-disk2 \
        --zone=$ZONE \
        --resource-policies=$VM_NAME-snap-grp
    

建立新的資料庫

  1. 建立 VM 執行個體的使用者名稱和密碼
  2. 使用遠端桌面連線至 VM,並使用上一步建立的使用者名稱和密碼登入。
  3. 在「開始」按鈕上按一下滑鼠右鍵 (或按下 Win+X 鍵),然後按一下「終端機 (系統管理員)」
  4. 按一下「是」,確認提升權限提示。
  5. 在開啟的終端機視窗中執行下列 PowerShell 指令碼。這個指令碼會初始化資料磁碟、以 64 KB 區塊大小格式化磁碟,並指派磁碟機代號。

    $availableDisks = Get-PhysicalDisk -CanPool $True
    $diskLetters = [char[]] (68..72) | Where-Object { !(Get-PSDrive $_ -ErrorAction SilentlyContinue) }
    $diskCount = 0
    foreach ($disk in $availableDisks) {
        $diskLetter = $diskLetters[$diskCount]
        $diskLabel = "$diskLetter-DataDisk"
        Initialize-Disk -Number $disk.DeviceId -PartitionStyle MBR -PassThru
    
        New-Partition -DiskNumber $disk.DeviceId -UseMaximumSize `
        -DriveLetter $diskLetter | Format-Volume -FileSystem NTFS `
        -NewFileSystemLabel $diskLabel -AllocationUnitSize 65536 -Confirm:$false
    
        New-Item -ItemType Directory -Path "$($diskLetter):\MSSQL"
        $diskCount = $diskCount +1
    }
    
  6. 開啟 SQL Server Management Studio (SSMS)。以管理員身分執行。

  7. 在「連線到伺服器」對話方塊中,確認伺服器名稱已設為 localhost,然後選取「連線」

  8. 在檔案選單中,依序選取「檔案」>「新增」>「查詢」,並使用目前的連線。

  9. 將下列程式碼複製到新開啟的查詢視窗。

    USE Master;
    GO
    CREATE DATABASE SnapBackupDB
    ON PRIMARY
    (
        NAME = 'SnapBackupDB_Data',
        FILENAME = 'D:\MSSQL\SnapBackupDB.mdf',
        SIZE = 500MB,
        MAXSIZE = UNLIMITED,
        FILEGROWTH = 64MB
    )
    LOG ON
    (
        NAME = 'SnapBackupDB_Log',
        FILENAME = 'E:\MSSQL\SnapBackupDB_log.ldf',
        SIZE = 250MB,
        MAXSIZE = 2GB,
        FILEGROWTH = 64MB
    );
    

部署快照指令碼

這項指令碼會自動執行完整程序,為連結至執行 SQL Server 的 VM 的磁碟建立應用程式一致性快照。

  1. 在同一個遠端桌面連線中開啟記事本。
  2. 複製下列指令碼的內容,然後貼到開啟的「記事本」視窗中。
  3. 將檔案儲存為 take-snapshot.ps1,並放在 c:\scripts

    # Import Modules
    Import-Module -Name SQLPS
    
    # Set variables
    $formattedTimestamp = Get-Date -Format 'yyyyMMdd-HHmmss'
    $backupLocation = 'D:\MSSQL\Backup'
    $dbName = 'SnapBackupDB'
    $bkmFile = Join-Path -Path $backupLocation  -ChildPath ("${dbName}_${formattedTimestamp}.bkm")
    $sqlServer = 'localhost'
    
    # SQL Commands
    $suspendDbCmd = "ALTER DATABASE [$dbName] SET SUSPEND_FOR_SNAPSHOT_BACKUP=ON WITH NO_WAIT;"
    $backupMetadataCmd = "BACKUP DATABASE [$dbName] TO DISK='$bkmFile' WITH METADATA_ONLY, FORMAT;"
    $unsuspendDbCmd = "ALTER DATABASE [$dbName] SET SUSPEND_FOR_SNAPSHOT_BACKUP=OFF;"
    
    # --- Helper Function for SQL Execution ---
    function Invoke-MySqlCommand {
        param(
            [Parameter(Mandatory=$true)]
            [string]$CommandText,
            [Parameter(Mandatory=$true)]
            [System.Data.SqlClient.SqlConnection]$SqlConnection
        )
        Write-Host "Executing SQL Command: $($CommandText)"
        try {
            $Command = New-Object System.Data.SqlClient.SqlCommand($CommandText, $SqlConnection)
            $Result = $Command.ExecuteNonQuery()
            return $true
        }
        catch {
            Write-Host "Error executing SQL Command: $($CommandText)`n$($_.Exception.Message)" -ForegroundColor Red
            return $false
        }
    }
    
    $sqlConn = New-Object System.Data.SqlClient.SqlConnection
    $sqlConn.ConnectionString = "server='$sqlServer';database='$dbName';Integrated Security=True;"
    $databaseSuspended = $false
    
    if (-not(Test-Path $backupLocation)) {
        New-Item -Path $backupLocation -ItemType directory -Force
    }
    
    try {
        $instanceName = (Invoke-RestMethod -Headers @{"Metadata-Flavor"="Google"} -Uri "http://metadata.google.internal/computeMetadata/v1/instance/name")
        $vmConfigString = (gcloud compute instances list --filter="name=('$instanceName')" --format=json --quiet)
        if ($LASTEXITCODE -ne 0) {
            Write-Host "Error querying gcloud for VM instances (exit code: $LASTEXITCODE). Exiting." -ForegroundColor Red
            exit 1
        }
        $vmConfig = $vmConfigString | ConvertFrom-Json
    
        # Open SQL Server connection
        $sqlConn.Open()
    
        # Suspend Database
        Write-Host "Suspending database '$dbName' for snapshot..."
        if (-not (Invoke-MySqlCommand -CommandText $suspendDbCmd -SqlConnection $sqlConn)) {
            Write-Host "Failed to suspend database. Exiting." -ForegroundColor Red
            exit 1
        }
        $databaseSuspended = $true
    
        # Take a disk snapshot
        try {
            $consistencyGroupListStr = (gcloud compute resource-policies list --filter='name:*-snap-grp' --format=json --quiet)
            if ($LASTEXITCODE -ne 0) { throw "gcloud resource-policies list failed (exit code: $LASTEXITCODE)." }
            $consistencyGroupList = $consistencyGroupListStr | ConvertFrom-Json
    
            if (@($consistencyGroupList).Count -eq 0) { throw "No consistency group found matching '*-snap-grp'." }
            if (@($consistencyGroupList).Count -gt 1) { Write-Warning "More than one consistency group found, using the first one." }
    
            $consistencyGroupSelfLink = $consistencyGroupList[0].selfLink
            $zoneName = Split-Path $vmConfig[0].zone -Leaf
            $snapshotName = "$($vmConfig.Name)-${formattedTimestamp}"
    
            $snapshotCmd = "gcloud compute instant-snapshot-groups create $snapshotName --source-consistency-group=$consistencyGroupSelfLink --zone=$zoneName --quiet"
            Invoke-Expression $snapshotCmd
            if ($LASTEXITCODE -ne 0) { throw "gcloud compute instant-snapshot-groups create failed (exit code: $LASTEXITCODE)." }
            Write-Host "Instant snapshot group created successfully."
        }
        catch {
            Write-Host "Error during snapshot creation: $($_.Exception.Message)" -ForegroundColor Red
            exit 1 # Exit after handling in finally
        }
    
        # Backup database metadata
        if (-not (Invoke-MySqlCommand -CommandText $backupMetadataCmd -SqlConnection $sqlConn)) {
            Write-Host "Failed to backup database metadata." -ForegroundColor Red
            exit 1
        }
    
        # Unsuspend Database
        Write-Host "Unsuspending database '$dbName'..."
        if (-not (Invoke-MySqlCommand -CommandText $unsuspendDbCmd -SqlConnection $sqlConn)) {
            Write-Host "Failed to unsuspend database after successful snapshot and metadata backup. Manual intervention may be required." -ForegroundColor Red
            exit 1
        }
        $databaseSuspended = $false # Successfully unsuspended
    
    }
    catch {
        Write-Host "An unhandled error occurred: $($_.Exception.Message)" -ForegroundColor Red
        exit 1
    }
    finally {
        if ($databaseSuspended -and ($sqlConn.State -eq [System.Data.ConnectionState]::Open)) {
            if (-not (Invoke-MySqlCommand -CommandText $unsuspendDbCmd -SqlConnection $sqlConn)) {
                Write-Host "Failed to unsuspend database in finally block. Manual intervention may be required." -ForegroundColor Red
            }
        }
    
        # Ensure SQL connection is closed
        if ($sqlConn.State -eq [System.Data.ConnectionState]::Open) {
            $sqlConn.Close()
        }
    }
    
    
  4. 在「開始」按鈕上按一下滑鼠右鍵 (或按下 Win+X 鍵),然後按一下「終端機 (系統管理員)」

  5. 按一下「是」,確認提升權限提示。

  6. 執行下列指令來執行指令碼。

        C:\scripts\take-snapshot.ps1
    

確認已建立即時快照

在 Cloud Shell 中執行下列指令:

  1. 確認指令碼已建立快照。

        gcloud compute instant-snapshots list --filter="name:$VM_NAME*"
    
  2. 列出即時快照群組。

        gcloud compute instant-snapshot-groups list --zones $ZONE
    
  3. 查看即時快照群組的詳細資料。

        gcloud compute instant-snapshot-groups describe  \
        INSTANT_SNAPSHOT_NAME --zone=$ZONE
    
  4. 複製 selfLink 值,以便在下一節中使用。

將即時快照還原到新磁碟

在 Google Cloud 控制台視窗中執行下列指令。

  1. 從即時快照一致性群組建立磁碟。

        gcloud compute disks bulk create --source-instant-snapshot-group \
        INSTANT_SNAPSHOT_NAME_URL \
        --source-instant-snapshot-group-region $REGION --zone=$ZONE
    
  2. 列出可用區中的磁碟,確認快照已還原。

        gcloud compute disks list --zones=$ZONE --filter="name:$VM_NAME-*"
    
  3. 從新建立的磁碟建立新的 VM。

    REGION=REGION
    ZONE=$REGION-a
    VM_NAME=VM_NAME
    gcloud compute instances create $VM_NAME \
        --machine-type c4-highmem-4 \
        --zone $ZONE \
        --network-interface subnet=default \
        --tags sql-server-instant-snapshot \
        --disk=name=BOOT_DISK_NAME,boot=yes,auto-delete=no \
        --disk=name=DATA_DISK_1_NAME,mode=rw,auto-delete=no \
        --disk=name=DATA_DISK_2_NAME,mode=rw,auto-delete=no
    

更改下列內容:

  • 區域:新執行個體的部署區域。
  • VM_NAME:新 SQL 執行個體的名稱。
  • BOOT_DISK_NAME:在上一個步驟中建立的開機磁碟名稱。
  • DATA_DISK_1_NAME:在上一個步驟中建立的第一個資料磁碟名稱。
  • DATA_DISK_2_NAME:在上一個步驟中建立的第二個資料磁碟名稱。

現在可以將磁碟連結至新的或現有的 VM。

清除所用資源

如要避免系統向您的 Google Cloud 帳戶收取本教學課程所用資源的費用,請刪除專案。

刪除專案

  1. 前往 Google Cloud 控制台的「Manage resources」(管理資源) 頁面。

    前往「Manage resources」(管理資源)

  2. 在專案清單中選取要刪除的專案,然後點選「Delete」(刪除)
  3. 在對話方塊中輸入專案 ID,然後按一下 [Shut down] (關閉) 以刪除專案。

後續步驟