使用即时快照备份 SQL Server

如果您的 SQL Server 数据库文件分布在多个磁盘上,您可以同时备份所有磁盘,从而保持应用和数据的一致性。您可以使用 Compute Engine 即时快照一致性组实现这种精确的时间点备份 ,该组 可让您备份一组磁盘中的数据

本教程介绍了如何使用 Compute Engine 快照以及 SQL Server 2022 及更高版本中提供的 Transact-SQL (T-SQL) 快照功能备份 SQL Server 数据库。 此解决方案支持 Windows 和 Linux 部署,并最大限度地减少对实时工作负载的性能影响。

工作原理

此工作流包含以下主要步骤,这些步骤由在计算实例上运行的脚本进行管理:

  1. 冻结数据库:脚本向 SQL Server 发送 T-SQL 命令,以 暂停目标数据库的所有写入操作。这可确保数据库文件处于一致的状态以进行备份。
  2. 创建即时快照:在数据库冻结期间,创建数据库的数据和日志文件所在的磁盘的即时快照组。这相当于使用硬件或服务级快照机制。 Google Cloud
  3. 记录和解冻:脚本向 SQL Server 发送另一个 T-SQL 命令,以记录 备份元数据。此命令会创建一个指向即时快照一致性 组的小型备份文件,并记录在 msdb 备份历史记录中。完成后, SQL Server 会自动解冻数据库并恢复正常运行。

整个过程通常在不到一秒的时间内完成,从而最大限度地缩短数据库的写入冻结时间。在冻结期间,可以读取数据,但无法写入数据。您可以为数据库设置 SUSPEND_FOR_SNAPSHOT_BACKUP=OFF,以手动取消冻结状态。

目标

在本教程中,您将学习如何完成以下任务:

  • 创建一个包含两个数据磁盘的 SQL Server 实例。
  • 创建一个新数据库,并将数据和日志文件放在单独的磁盘上。
  • 为运行 SQL Server 的虚拟机的所有磁盘创建一个一致性组。
  • 创建磁盘组的即时快照。
  • 通过即时快照创建新磁盘。

费用

在本文档中,您将使用的以下收费组件: Google Cloud

您可使用 价格计算器 根据您的预计使用情况来估算费用。

新 Google Cloud 用户可能有资格申请免费试用

完成本文档中描述的任务后,您可以通过删除所创建的资源来避免继续计费。如需了解详情,请参阅清理

准备工作

  1. 在本教程中,您需要一个 Google Cloud 项目。您可以创建一个新项目,也可以选择现有项目:

    1. 在 Google Cloud 控制台的项目选择器页面上, 选择或创建一个 Google Cloud 项目。

      选择或创建项目所需的角色

      • 选择项目:选择项目不需要特定的 IAM 角色,您可以选择已被授予角色的任何项目。
      • 创建项目:如需创建项目,您需要具有 Project Creator 角色 (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. 将虚拟机的磁盘添加到一致性组。

    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. 为虚拟机实例创建用户名和密码
  2. 使用远程桌面连接到虚拟机 然后使用上一步中创建的用户名和密码登录。
  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 的虚拟机所挂接的磁盘进行应用一致性快照所需的完整过程。

  1. 使用相同的远程桌面会话,打开记事本。
  2. 复制以下脚本的内容,然后粘贴到打开的记事本窗口中。
  3. 将文件另存为 c:\scripts 中的 take-snapshot.ps1

    # 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. 通过新创建的磁盘创建一个新虚拟机。

    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:在上一步 中创建的第二个数据磁盘的名称。

您现在可以将磁盘挂接到新的或现有的虚拟机。

清理

为避免系统因本教程中使用的资源 向您的 Google Cloud 账号收取费用,请删除项目。

删除项目

  1. 在 Google Cloud 控制台中,前往 管理资源 页面。

    转到“管理资源”

  2. 在项目列表中,选择要删除的项目,然后点击删除
  3. 在对话框中输入项目 ID,然后点击 关闭以删除项目。

后续步骤

  • 探索有关 Google Cloud 的参考架构、图表和最佳做法。查看我们的 Cloud 架构中心