使用 PowerShell 创建和管理弹性作业

适用于:Azure SQL 数据库

本文通过教程和示例说明了如何使用 PowerShell 来完成弹性作业的入门。 弹性作业可以跨多个数据库并行运行一个或多个 Transact-SQL (T-SQL) 脚本。

本端到端教程介绍跨多个数据库运行查询所需的步骤:

  • 创建弹性作业代理
  • 创建作业凭据,使作业可以在其目标上执行脚本
  • 定义要对其运行作业的目标(服务器、弹性池、数据库)
  • 在目标数据库中创建数据库范围的凭据,以便代理在连接后执行作业
  • 创建作业
  • 向作业添加作业步骤
  • 开始执行作业
  • 监视作业

先决条件

弹性数据库作业具有一组 PowerShell cmdlet。

这些 cmdlet 更新于 2023 年 11 月。

安装最新的弹性作业 cmdlet

如果没有 Azure 订阅,请在开始前创建一个试用订阅

如果上不存在,请安装最新版本的 Az.SqlSqlServer 模块。 以管理访问权限在 PowerShell 中运行以下命令。

# installs the latest PackageManagement and PowerShellGet packages
Find-Package PackageManagement | Install-Package -Force
Find-Package PowerShellGet | Install-Package -Force

# Restart your powershell session with administrative access

# Install and import the Az.Sql module, then confirm
Install-Module -Name Az.Sql
Import-Module Az.Sql
Install-Module -Name SqlServer
Import-Module SqlServer

有关详细信息,请参阅安装 SQL Server PowerShell 模块

创建所需资源

创建弹性作业代理需要将一个数据库(S1 或更高版本)用作弹性作业数据库

以下脚本创建新的资源组、服务器以及可用作弹性作业数据库的数据库。 第二个脚本创建另一个服务器,其中包含两个用于对其执行作业的空数据库。

弹性作业没有特定的命名要求,因此可以使用所需的任何命名约定,只要其符合 Azure 要求即可。 如果已创建服务器的空白数据库作为弹性作业数据库,请跳至创建弹性作业代理

使用弹性作业专用终结点时,无需配置 New-AzSqlServerFirewallRule 的防火墙规则。

# Sign in to your Azure account
Connect-AzAccount -EnvironmentName AzureChinaCLoud

# The SubscriptionId in which to create these objects
$SubscriptionId = '<your subscription id>'
# Set subscription context, important if you have access to more than one subscription.
Set-AzContext -SubscriptionId $subscriptionId 

# Create a resource group
Write-Output "Creating a resource group..."
$resourceGroupName = Read-Host "Please enter a resource group name"
$location = Read-Host "Please enter an Azure Region"
$rg = New-AzResourceGroup -Name $resourceGroupName -Location $location
$rg

# Create an Azure SQL logical server
Write-Output "Creating a server..."
$agentServerName = Read-Host "Please enter an agent server name"
$agentServerName = $agentServerName + "-" + [guid]::NewGuid()
$adminLogin = Read-Host "Please enter the server admin name"
$adminPassword = Read-Host "Please enter the server admin password"
$adminPasswordSecure = ConvertTo-SecureString -String $AdminPassword -AsPlainText -Force
$adminCred = New-Object -TypeName "System.Management.Automation.PSCredential" -ArgumentList $adminLogin, $adminPasswordSecure
$parameters = @{
    ResourceGroupName = $resourceGroupName 
    Location = $location
    ServerName = $agentServerName 
    SqlAdministratorCredentials = ($adminCred)    
}
$agentServer = New-AzSqlServer @parameters

# Set server firewall rules to allow all Azure IPs
# Unnecessary if using an elastic jobs private endpoint
Write-Output "Creating a server firewall rule..."
$agentServer | New-AzSqlServerFirewallRule -AllowAllAzureIPs -FirewallRuleName "Allowed IPs"
$agentServer

# Create the job database
Write-Output "Creating a blank database to be used as the Job Database..."
$jobDatabaseName = "JobDatabase"
$parameters = @{
    ResourceGroupName = $resourceGroupName 
    ServerName = $agentServerName 
    DatabaseName = $jobDatabaseName 
    RequestedServiceObjectiveName = "S1"
}
$jobDatabase = New-AzSqlDatabase @parameters
$jobDatabase
# Create a target server and sample databases - uses the same credentials
Write-Output "Creating target server..."
$targetServerName = Read-Host "Please enter a target server name"
$targetServerName = $targetServerName + "-" + [guid]::NewGuid()
$parameters = @{
    ResourceGroupName= $resourceGroupName
    Location= $location 
    ServerName= $targetServerName
    ServerVersion= "12.0"
    SqlAdministratorCredentials= ($adminCred)
}
$targetServer = New-AzSqlServer @parameters

# Set target server firewall rules to allow all Azure IPs
# Unnecessary if using an elastic jobs private endpoint
$targetServer | New-AzSqlServerFirewallRule -AllowAllAzureIPs 

# Set the target firewall to include your desired IP range. 
# Change the following -StartIpAddress and -EndIpAddress values.
$parameters = @{
    StartIpAddress = "0.0.0.0" 
    EndIpAddress = "0.0.0.0"
    FirewallRuleName = "AllowAll"
}
$targetServer | New-AzSqlServerFirewallRule @parameters
$targetServer

# Create two sample databases to execute jobs against
$parameters = @{
    ResourceGroupName = $resourceGroupName 
    ServerName = $targetServerName 
    DatabaseName = "database1"
}
$db1 = New-AzSqlDatabase @parameters
$db1
$parameters = @{
    ResourceGroupName = $resourceGroupName 
    ServerName = $targetServerName 
    DatabaseName = "database2"
}
$db2 = New-AzSqlDatabase @parameters
$db2

创建弹性作业代理

弹性作业代理是用于创建、运行和管理作业的 Azure 资源。 此代理执行的作业是按计划的,或者是一次性的。 弹性作业中的所有日期和时间均为 UTC 时区。

New-AzSqlElasticJobAgent cmdlet 要求 Azure SQL 数据库中已存在一个数据库,因此 resourceGroupNameserverNamedatabaseName 参数必须都指向现有资源。 同样,Set-AzSqlElasticJobAgent 可用于修改弹性作业代理。

要使用 Microsoft Entra 身份验证和用户分配的托管标识创建新的弹性作业代理,请使用 IdentityTypeNew-AzSqlElasticJobAgentIdentityID 参数 :

Write-Output "Creating job agent..."
$agentName = Read-Host "Please enter a name for your new elastic job agent"
$parameters = @{
    Name = $agentName 
    IdentityType = "UserAssigned" 
    IdentityID = "/subscriptions/abcd1234-caaf-4ba9-875d-f1234/resourceGroups/contoso-jobDemoRG/providers/Microsoft.ManagedIdentity/userAssignedIdentities/contoso-UMI"
}
$jobAgent = $jobDatabase | New-AzSqlElasticJobAgent @parameters
$jobAgent

要使用数据库范围的凭据创建新的弹性作业代理,未提供 IdentityTypeIdentityID

创建作业身份验证

弹性作业代理必须能够对每个目标服务器或数据库进行身份验证。

创建作业代理身份验证中所述:

将 Microsoft Entra 身份验证与 UMI 配合使用以对目标进行身份验证

要按照建议将 Microsoft Entra(以前称为 Azure Active Directory)身份验证与用户分配的托管标识 (UMI) 配合使用,请执行以下步骤。 弹性作业代理通过 Entra 身份验证连接到所需的目标逻辑服务器/数据库。

请注意,除了登录名和数据库用户,还在以下脚本中添加了 GRANT 命令。 这些权限是为此示例作业选择的脚本所需要的。 作业可能需要不同的权限才能运行。 由于此示例会在目标数据库中创建新表,因此每个目标数据库中的数据库用户需要适当的权限才能成功运行。

在每个目标服务器/数据库中,创建映射到 UMI 的包含用户。

  • 如果弹性作业具有逻辑服务器或池目标,则必须在目标逻辑服务器的 master 数据库中创建映射到 UMI 的包含用户。
  • 例如,若要根据名为 job-agent-UMI 的用户分配的托管标识 (UMI) 在 master 数据库中创建包含的数据库登录名,并在用户数据库中创建用户:
$targetServer = '<target server name>'
$adminLogin = '<username>'
$adminPassword = '<password>'

# For the target logical server, in the master database
# Create the login named [job-agent-UMI] based on the UMI [job-agent-UMI], and a user
$params = @{
  'database' = 'master'
  'serverInstance' =  $targetServer.ServerName + '.database.chinacloudapi.cn'
  'username' = $adminLogin
  'password' = $adminPassword
  'outputSqlErrors' = $true
  'query' = 'CREATE LOGIN [job-agent-UMI] FROM EXTERNAL PROVIDER;'
}
Invoke-SqlCmd @params
$params.query = "CREATE USER [job-agent-UMI] FROM LOGIN [job-agent-UMI]"
Invoke-SqlCmd @params

# For each target database in the target logical server
# Create a database user from the job-agent-UMI login 
$targetDatabases = @( $db1.DatabaseName, $Db2.DatabaseName )
$createJobUserScript =  "CREATE USER [job-agent-UMI] FROM LOGIN [job-agent-UMI]"

# Grant permissions as necessary. For example ALTER and CREATE TABLE:
$grantAlterSchemaScript = "GRANT ALTER ON SCHEMA::dbo TO [job-agent-UMI]" 
$grantCreateScript = "GRANT CREATE TABLE TO [job-agent-UMI]"

$targetDatabases | % {
  $params.database = $_
  $params.query = $createJobUserScript
  Invoke-SqlCmd @params
  $params.query = $grantAlterSchemaScript
  Invoke-SqlCmd @params
  $params.query = $grantCreateScript
  Invoke-SqlCmd @params
}

使用数据库范围的凭据对目标进行身份验证

作业代理在执行时使用目标组指定的凭据并执行脚本。 也可使用这些数据库范围的凭据连接到 master 数据库,以便发现服务器或弹性池中的所有数据库,前提是将这其中的一个用作目标组成员类型。

必须在作业数据库中创建数据库范围的凭据。 所有目标数据库必须有一个具有足够权限的登录名,否则作业无法成功完成。

请注意,除了图像中的凭据,还在以下脚本中添加了 GRANT 命令。 这些权限是为此示例作业选择的脚本所需要的。 作业可能需要不同的权限才能运行。 由于此示例会在目标数据库中创建新表,因此每个目标数据库中的数据库用户需要适当的权限才能成功运行。

每个目标服务器/数据库的登录名/用户必须采用与作业用户的数据库范围凭据的标识相同的名称,并采用与作业用户的数据库范围凭据相同的密码。 PowerShell 脚本使用 <strong jobuser password here> 时,请在整个过程中使用相同的密码。

以下示例使用数据库范围的凭据。 若要创建所需的作业凭据(在作业数据库中),请运行以下脚本,该脚本使用 SQL 身份验证连接到目标服务器/数据库:

# For the target logical server, in the master database
# Create the master user login, master user, and job user login
$targetServer = '<target server name>'
$adminLogin = '<username>'
$adminPassword = '<password>'

$params = @{
  'database' = 'master'
  'serverInstance' =  $targetServer.ServerName + '.database.chinacloudapi.cn'
  'username' = $adminLogin
  'password' = $adminPassword
  'outputSqlErrors' = $true
  'query' = 'CREATE LOGIN adminuser WITH PASSWORD=''<strong adminuser password here>'''
}
Invoke-SqlCmd @params
$params.query = "CREATE USER adminuser FROM LOGIN adminuser"
Invoke-SqlCmd @params
$params.query = 'CREATE LOGIN jobuser WITH PASSWORD=''<strong jobuser password here>'''
Invoke-SqlCmd @params

# For each target database in the target logical server
# Create the jobuser from jobuser login and check permission for script execution
$targetDatabases = @( $db1.DatabaseName, $Db2.DatabaseName )
$createJobUserScript =  "CREATE USER jobuser FROM LOGIN jobuser"

# Grant permissions as necessary. For example ALTER and CREATE TABLE:
$grantAlterSchemaScript = "GRANT ALTER ON SCHEMA::dbo TO jobuser"
$grantCreateScript = "GRANT CREATE TABLE TO jobuser"

$targetDatabases | % {
  $params.database = $_
  $params.query = $createJobUserScript
  Invoke-SqlCmd @params
  $params.query = $grantAlterSchemaScript
  Invoke-SqlCmd @params
  $params.query = $grantCreateScript
  Invoke-SqlCmd @params
}

# Create job credential in job database for admin user
Write-Output "Creating job credentials..."
$loginPasswordSecure = (ConvertTo-SecureString -String '<strong jobuser password here>' -AsPlainText -Force)
$loginadminuserPasswordSecure = (ConvertTo-SecureString -String '<strong adminuser password here>' -AsPlainText -Force)

$adminCred = New-Object -TypeName "System.Management.Automation.PSCredential" -ArgumentList "adminuser", $loginadminuserPasswordSecure
$adminCred = $jobAgent | New-AzSqlElasticJobCredential -Name "adminuser" -Credential $adminCred

$jobCred = New-Object -TypeName "System.Management.Automation.PSCredential" -ArgumentList "jobuser", $loginPasswordSecure
$jobCred = $jobAgent | New-AzSqlElasticJobCredential -Name "jobuser" -Credential $jobCred

定义目标服务器和数据库

目标组定义可以在其上执行作业步骤的数据库集(包含一个或多个数据库)。

以下代码片段将创建两个目标组:serverGroupserverGroupExcludingDb2serverGroup 的目标是执行时存在于服务器上的所有数据库,serverGroupExcludingDb2 的目标是服务器上的所有数据库,TargetDb2 除外:

Write-Output "Creating test target groups..."
# create ServerGroup target group
$serverGroup = $jobAgent | New-AzSqlElasticJobTargetGroup -Name 'ServerGroup'
$serverGroup | Add-AzSqlElasticJobTarget -ServerName $targetServerName -RefreshCredentialName $adminCred.CredentialName

# create ServerGroup with an exclusion of db2
$serverGroupExcludingDb2 = $jobAgent | New-AzSqlElasticJobTargetGroup -Name 'ServerGroupExcludingDb2'
$serverGroupExcludingDb2 | Add-AzSqlElasticJobTarget -ServerName $targetServerName -RefreshCredentialName $adminCred.CredentialName
$serverGroupExcludingDb2 | Add-AzSqlElasticJobTarget -ServerName $targetServerName -Database $db2.DatabaseName -Exclude

创建作业和步骤

此示例为要运行的作业定义作业和两个作业步骤。 第一个作业步骤 (step1) 在目标组 ServerGroup 的每个数据库中创建新表 (Step1Table)。 第二个作业步骤 (step2) 在除 TargetDb2 之外的每个数据库中创建新表 (Step2Table),因为之前定义的目标组已指定将其排除。

Write-Output "Creating a new job..."
$jobName = "Job1"
$job = $jobAgent | New-AzSqlElasticJob -Name $jobName -RunOnce
$job

Write-Output "Creating job steps..."
$sqlText1 = "IF NOT EXISTS (SELECT * FROM sys.tables WHERE object_id = object_id('Step1Table')) CREATE TABLE [dbo].[Step1Table]([TestId] [int] NOT NULL);"
$sqlText2 = "IF NOT EXISTS (SELECT * FROM sys.tables WHERE object_id = object_id('Step2Table')) CREATE TABLE [dbo].[Step2Table]([TestId] [int] NOT NULL);"

$job | Add-AzSqlElasticJobStep -Name "step1" -TargetGroupName $serverGroup.TargetGroupName -CredentialName $jobCred.CredentialName -CommandText $sqlText1
$job | Add-AzSqlElasticJobStep -Name "step2" -TargetGroupName $serverGroupExcludingDb2.TargetGroupName -CredentialName $jobCred.CredentialName -CommandText $sqlText2

运行作业

若要立即启动作业,请运行以下命令:

Write-Output "Start a new execution of the job..."
$jobExecution = $job | Start-AzSqlElasticJob
$jobExecution

成功完成以后,应该会在 TargetDb1 中看到两个新表,在 TargetDb2 中只看到一个新表。

还可以将作业计划为在以后运行。

重要

弹性作业中的所有开始时间均处于 UTC 时区。

若要计划在特定时间运行的作业,请运行以下命令:

# run every hour starting from now
$job | Set-AzSqlElasticJob -IntervalType Hour -IntervalCount 1 -StartTime (Get-Date) -Enable

监视作业执行操作的状态

以下代码片段获取作业执行详细信息:

# get the latest 10 executions run
$jobAgent | Get-AzSqlElasticJobExecution -Count 10

# get the job step execution details
$jobExecution | Get-AzSqlElasticJobStepExecution

# get the job target execution details
$jobExecution | Get-AzSqlElasticJobTargetExecution -Count 2

下表列出了可能的作业执行状态:

状态 说明
创建时间 作业执行刚刚创建,还没有进行。
InProgress 作业执行目前正在进行中。
WaitingForRetry 作业执行无法完成其操作,正在等待重试。
成功 作业执行已成功完成。
SucceededWithSkipped 作业执行已成功完成,但跳过了它的一些子项。
失败 作业执行失败,已用尽重试次数。
TimedOut 作业执行已超时。
已取消 作业执行已取消。
已跳过 已跳过作业执行,因为同一作业步骤的另一个执行已在同一目标上运行。
WaitingForChildJobExecutions 作业执行正在等待其子执行完成。

清理资源

通过删除资源组来删除本教程中创建的 Azure 资源。

提示

如果打算继续使用这些作业,请勿清理本文中创建的资源。

Remove-AzResourceGroup -ResourceGroupName $resourceGroupName

下一步