SQL Server 管理系列(第五期):自动化运维——SQL Agent 作业与多服务器管理
上一期我们学习了高可用架构。这一期,我们进入 DBA 日常工作的核心——自动化运维。你是否遇到过:每天凌晨 3 点起床做数据库备份?服务器数量增长后,逐一登录执行维护任务?半夜收到磁盘空间告警,却只能手动清理?这一期,我们教你用 SQL Server Agent 把重复性工作交给机器,让 DBA 从“消防员”变成“架构师”。
一、SQL Server Agent 概述
1.1 Agent 的核心组件
┌─────────────────────────────────────────────────────────────┐
│ SQL Server Agent │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 作业 │ │ 操作员 │ │ 警报 │ │
│ │ Job │ │ Operator │ │ Alert │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │ │
│ └────────────────┼────────────────┘ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 步骤 (Steps) │ │
│ │ T-SQL │ PowerShell │ CmdExec │ SSIS │ 复制 │ 分析服务 │ │
│ └─────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 调度 (Schedules) │ │
│ │ 一次性 │ 重复执行 │ 按CPU空闲 │ 启动时 │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
1.2 启用和配置 SQL Agent
-- 查看 Agent 状态
SELECT
name,
is_enabled,
CASE status
WHEN 1 THEN 'Running'
WHEN 2 THEN 'Paused'
WHEN 3 THEN 'Stopped'
WHEN 4 THEN 'Starting'
END AS AgentStatus
FROM msdb.dbo.sysjobscheduler
-- 启动 SQL Agent(使用 SQL Server 配置管理器或 PowerShell)
Start-Service SQLSERVERAGENT -- 默认实例
Start-Service SQLAgent$InstanceName -- 命名实例
-- 查看 Agent 错误日志
EXEC xp_readerrorlog 0, 1, 'Agent', NULL, '2024-01-01', '2024-01-02', N'DESC'
二、作业(Job)深度配置
2.1 创建作业的标准模板
USE msdb;
GO
-- 1. 创建作业
EXEC dbo.sp_add_job
@job_name = N'DBA_DailyMaintenance',
@enabled = 1,
@description = N'每日数据库维护:更新统计信息、检查一致性、清理历史',
@owner_login_name = N'sa',
@notify_level_eventlog = 2, -- 失败时写入日志
@notify_level_email = 2, -- 失败时发送邮件
@notify_level_netsend = 0,
@notify_level_page = 0;
-- 2. 创建步骤1:更新统计信息
EXEC dbo.sp_add_jobstep
@job_name = N'DBA_DailyMaintenance',
@step_name = N'更新统计信息',
@step_id = 1,
@command = N'
EXEC sp_updatestats;
-- 或针对特定表
UPDATE STATISTICS YourDB.dbo.Orders WITH FULLSCAN;
',
@database_name = N'YourDB',
@on_success_action = 3, -- 成功则继续下一步
@on_fail_action = 2; -- 失败则终止
-- 3. 创建步骤2:检查数据库完整性
EXEC dbo.sp_add_jobstep
@job_name = N'DBA_DailyMaintenance',
@step_name = N'DBCC CHECKDB',
@step_id = 2,
@command = N'DBCC CHECKDB(YourDB) WITH NO_INFOMSGS, PHYSICAL_ONLY',
@database_name = N'YourDB',
@on_success_action = 3,
@on_fail_action = 1; -- 失败后报告成功(记录错误但不终止作业)
-- 4. 创建调度(每天凌晨2点)
EXEC dbo.sp_add_schedule
@schedule_name = N'Daily_2AM',
@freq_type = 4, -- 每天
@freq_interval = 1, -- 每1天
@active_start_time = 20000, -- 02:00:00
@active_end_time = 235959;
EXEC dbo.sp_attach_schedule
@job_name = N'DBA_DailyMaintenance',
@schedule_name = N'Daily_2AM';
-- 5. 添加目标服务器(多服务器管理)
EXEC dbo.sp_add_jobserver
@job_name = N'DBA_DailyMaintenance',
@server_name = N'(local)';
-- 6. 立即启动作业(测试)
EXEC dbo.sp_start_job @job_name = N'DBA_DailyMaintenance';
2.2 作业步骤类型详解
| 类型 | 适用场景 | 示例 |
|---|---|---|
| T-SQL | 数据库操作、维护 | 备份、统计信息、索引重建 |
| CmdExec | 操作系统命令、PowerShell | 磁盘清理、文件复制 |
| PowerShell | 复杂文件操作、云 API | Azure 备份、S3 上传 |
| SSIS | ETL 数据导入导出 | 数据仓库加载 |
| 复制 | 复制代理 | 同步订阅 |
| 分析服务 | SSAS 处理 | 多维数据集刷新 |
-- CmdExec 示例:清理旧备份文件
EXEC dbo.sp_add_jobstep
@job_name = N'Backup_Cleanup',
@step_name = N'删除7天前备份',
@subsystem = N'CmdExec',
@command = N'
forfiles /p "G:\Backup" /s /m *.bak /d -7 /c "cmd /c del @file"
forfiles /p "G:\Backup" /s /m *.trn /d -7 /c "cmd /c del @file"
';
-- PowerShell 示例:发送备份报告到 S3
EXEC dbo.sp_add_jobstep
@job_name = N'Backup_To_Azure',
@step_name = N'上传到Azure Blob',
@subsystem = N'PowerShell',
@command = N'
$ctx = New-AzStorageContext -StorageAccountName "myaccount" -StorageAccountKey "key"
$blob = Get-AzStorageBlob -Container "backups" -Context $ctx
Set-AzStorageBlobContent -File "G:\Backup\YourDB.bak" -Container "backups" -Context $ctx
';
2.3 作业调度频率配置
-- 调度参数说明
-- freq_type: 1=一次, 4=每天, 8=每周, 16=每月, 32=每月相对, 64=启动时, 128=CPU空闲
-- 每周一到周五 09:00 执行
EXEC dbo.sp_add_schedule
@schedule_name = N'Weekday_9AM',
@freq_type = 8, -- 每周
@freq_interval = 62, -- 周一至周五(2+4+8+16+32)
@freq_recurrence_factor = 1,
@active_start_time = 90000; -- 09:00:00
-- 每月最后一天 23:00 执行
EXEC dbo.sp_add_schedule
@schedule_name = N'LastDayOfMonth',
@freq_type = 16, -- 每月
@freq_interval = 1, -- 第1天(但配合 @freq_relative_interval)
@freq_relative_interval = 10, -- 最后一周
@freq_recurrence_factor = 1,
@active_start_time = 230000;
-- 每15分钟执行(用于日志备份)
EXEC dbo.sp_add_schedule
@schedule_name = N'Every_15Min',
@freq_type = 4, -- 每天
@freq_interval = 1,
@freq_subday_type = 4, -- 分钟间隔
@freq_subday_interval = 15,
@active_start_time = 0;
三、操作员(Operator)与警报(Alert)
3.1 创建操作员
-- 创建邮件操作员
EXEC msdb.dbo.sp_add_operator
@name = N'DBA_Team',
@enabled = 1,
@email_address = N'dba@company.com',
@pager_email_address = N'dba-pager@company.com', -- 紧急通知
@weekday_pager_start_time = 80000, -- 08:00-20:00
@weekday_pager_end_time = 200000,
@saturday_pager_start_time = 0, -- 全天候
@saturday_pager_end_time = 235959,
@sunday_pager_start_time = 0,
@sunday_pager_end_time = 235959,
@netsend_address = N'DBA_Computer';
-- 创建多个操作员用于不同级别告警
EXEC sp_add_operator @name = N'Critical_Team', @email_address = N'critical@company.com';
EXEC sp_add_operator @name = N'Warning_Team', @email_address = N'warning@company.com';
3.2 配置数据库邮件
-- 1. 启用 Database Mail
EXEC sp_configure 'show advanced options', 1;
RECONFIGURE;
EXEC sp_configure 'Database Mail XPs', 1;
RECONFIGURE;
-- 2. 创建邮件配置文件
EXEC msdb.dbo.sysmail_add_profile_sp
@profile_name = 'SQL_Alert_Profile',
@description = 'SQL Server 告警邮件配置';
-- 3. 添加邮件账户
EXEC msdb.dbo.sysmail_add_account_sp
@account_name = 'SQL_Alert_Account',
@email_address = 'sqlalert@company.com',
@display_name = 'SQL Server 告警',
@mailserver_name = 'smtp.company.com',
@port = 25;
-- 4. 关联账户到配置文件
EXEC msdb.dbo.sysmail_add_profileaccount_sp
@profile_name = 'SQL_Alert_Profile',
@account_name = 'SQL_Alert_Account',
@sequence_number = 1;
-- 5. 设置默认配置文件
EXEC msdb.dbo.sysmail_configure_sp
@parameter_name = 'DefaultProfile',
@parameter_value = 'SQL_Alert_Profile';
-- 6. 配置 SQL Agent 使用该配置文件
EXEC msdb.dbo.sp_set_sqlagent_properties
@databasemail_profile = 'SQL_Alert_Profile';
3.3 创建警报
-- 严重错误警报(错误级别 19-25)
EXEC msdb.dbo.sp_add_alert
@name = N'严重数据库错误',
@message_id = 0,
@severity = 019, -- 严重级别 19-25
@enabled = 1,
@delay_between_responses = 60, -- 60秒内不重复告警
@notification_message = N'检测到严重数据库错误,请立即处理!',
@include_event_description_in = 1,
@database_name = NULL,
@event_description_keyword = NULL;
-- 特定错误号警报(如死锁 1205)
EXEC msdb.dbo.sp_add_alert
@name = N'死锁检测',
@message_id = 1205,
@severity = 0,
@enabled = 1,
@delay_between_responses = 300,
@notification_message = N'检测到死锁,请检查应用程序。';
-- 性能条件警报(磁盘空间不足)
EXEC msdb.dbo.sp_add_alert
@name = N'磁盘空间不足',
@performance_condition = N'LogicalDisk|% Free Space|C:|<|10',
@category_name = N'Performance',
@severity = 0,
@enabled = 1;
-- 关联警报到操作员
EXEC msdb.dbo.sp_add_notification
@alert_name = N'死锁检测',
@operator_name = N'DBA_Team',
@notification_method = 1; -- 1=邮件, 2=寻呼, 4=网络发送, 7=全部
四、多服务器管理(MSX/TSX)
4.1 架构说明
┌─────────────────────────────────────────────────────────────┐
│ 主服务器 (MSX) │
│ Master Server │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ 作业1 │ │ 作业2 │ │ 作业3 │ │
│ │ 备份 │ │ 索引 │ │ 一致性 │ │
│ └────┬────┘ └────┬────┘ └────┬────┘ │
│ │ │ │ │
│ └────────────┼────────────┘ │
│ ▼ │
│ ┌───────────────┐ │
│ │ 分发作业 │ │
│ └───────┬───────┘ │
└────────────────────┼─────────────────────────────────────────┘
│
┌───────────┼───────────┬───────────────┐
▼ ▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ 目标1 │ │ 目标2 │ │ 目标3 │ │ 目标N │
│ TSX │ │ TSX │ │ TSX │ │ TSX │
└─────────┘ └─────────┘ └─────────┘ └─────────┘
4.2 配置主服务器(MSX)
-- 1. 在目标服务器上登记到主服务器(在目标服务器执行)
EXEC msdb.dbo.sp_msx_enlist
@msx_server_name = N'CentralDBServer';
-- 2. 查看登记的服务器
EXEC msdb.dbo.sp_help_targetserver;
-- 3. 强制目标服务器刷新作业列表
EXEC msdb.dbo.sp_post_msx_operation
@operation = 'RE-ENLIST',
@object_type = 'SERVER',
@specific_target_server = N'TargetServer01';
-- 4. 从主服务器删除目标服务器
EXEC msdb.dbo.sp_msx_defect
@target_server = N'TargetServer01';
4.3 创建多服务器作业
-- 在主服务器上创建作业(会自动分发到所有目标服务器)
EXEC dbo.sp_add_job
@job_name = N'MSX_DailyBackup',
@enabled = 1,
@description = N'所有服务器的每日备份',
@owner_login_name = N'sa';
-- 添加步骤(在所有目标服务器上执行)
EXEC dbo.sp_add_jobstep
@job_name = N'MSX_DailyBackup',
@step_name = N'完整备份',
@command = N'
DECLARE @dbname sysname
DECLARE cur CURSOR FOR
SELECT name FROM sys.databases
WHERE name NOT IN (''tempdb'') AND state = 0
OPEN cur
FETCH NEXT FROM cur INTO @dbname
WHILE @@FETCH_STATUS = 0
BEGIN
BACKUP DATABASE @dbname
TO DISK = ''D:\Backup\'' + @dbname + ''_'' + CONVERT(VARCHAR, GETDATE(), 112) + ''.bak''
WITH COMPRESSION
FETCH NEXT FROM cur INTO @dbname
END
CLOSE cur
DEALLOCATE cur
';
-- 调度(统一执行时间)
EXEC dbo.sp_add_schedule
@schedule_name = N'Global_Midnight',
@freq_type = 4,
@freq_interval = 1,
@active_start_time = 0;
EXEC dbo.sp_attach_schedule
@job_name = N'MSX_DailyBackup',
@schedule_name = N'Global_Midnight';
-- 分发到所有目标服务器
EXEC dbo.sp_add_jobserver
@job_name = N'MSX_DailyBackup',
@server_name = N'(ALL)';
-- 查看作业在各目标服务器的状态
SELECT
sj.name AS JobName,
sjs.last_run_date,
sjs.last_run_time,
sjs.last_outcome_message,
st.name AS TargetServer
FROM msdb.dbo.sysjobs sj
JOIN msdb.dbo.sysjobservers sjs ON sj.job_id = sjs.job_id
JOIN msdb.dbo.systargetservers st ON sjs.server_id = st.server_id
WHERE sj.name = 'MSX_DailyBackup';
五、运维自动化实战案例
5.1 磁盘空间监控与自动清理
-- 作业:每日检查磁盘空间,不足时自动清理
-- 步骤1:检查磁盘空间(T-SQL)
CREATE PROC dbo.CheckDiskSpace
AS
BEGIN
CREATE TABLE #DiskSpace (Drive CHAR(1), FreeMB INT)
INSERT INTO #DiskSpace
EXEC xp_fixeddrives
IF EXISTS (SELECT 1 FROM #DiskSpace WHERE FreeMB < 10240) -- 小于10GB
THROW 50001, '磁盘空间不足', 1
END
-- 步骤2:自动清理(CmdExec)
-- forfiles /p "D:\Backup" /s /m *.bak /d -3 /c "cmd /c del @file"
-- 步骤3:发送报告
EXEC msdb.dbo.sp_send_dbmail
@profile_name = 'SQL_Alert_Profile',
@recipients = 'dba@company.com',
@subject = '磁盘空间报告',
@body = '当前磁盘空间:...';
5.2 索引维护自动化
-- 智能索引维护:根据碎片率决定重组或重建
CREATE PROC dbo.IndexMaintenance
@DBName NVARCHAR(128),
@MinFragmentation INT = 5,
@MaxFragmentation INT = 30
AS
BEGIN
DECLARE @SQL NVARCHAR(MAX)
DECLARE @TableName NVARCHAR(128)
DECLARE @IndexName NVARCHAR(128)
DECLARE @Fragmentation DECIMAL(5,2)
DECLARE idx_cursor CURSOR FOR
SELECT
OBJECT_SCHEMA_NAME(ips.object_id) + '.' + OBJECT_NAME(ips.object_id) AS TableName,
i.name AS IndexName,
ips.avg_fragmentation_in_percent
FROM sys.dm_db_index_physical_stats(DB_ID(@DBName), NULL, NULL, NULL, 'LIMITED') ips
JOIN sys.indexes i ON ips.object_id = i.object_id AND ips.index_id = i.index_id
WHERE ips.avg_fragmentation_in_percent > @MinFragmentation
AND i.name IS NOT NULL
AND ips.page_count > 1000 -- 忽略小表
OPEN idx_cursor
FETCH NEXT FROM idx_cursor INTO @TableName, @IndexName, @Fragmentation
WHILE @@FETCH_STATUS = 0
BEGIN
IF @Fragmentation < @MaxFragmentation
SET @SQL = 'ALTER INDEX [' + @IndexName + '] ON ' + @TableName + ' REORGANIZE'
ELSE
SET @SQL = 'ALTER INDEX [' + @IndexName + '] ON ' + @TableName + ' REBUILD'
EXEC sp_executesql @SQL
FETCH NEXT FROM idx_cursor INTO @TableName, @IndexName, @Fragmentation
END
CLOSE idx_cursor
DEALLOCATE idx_cursor
END
-- 调度:每周日凌晨执行
-- EXEC dbo.IndexMaintenance 'YourDB', 5, 30
5.3 备份完整性自动校验
-- 作业:恢复演练(自动验证备份文件)
CREATE PROC dbo.ValidateBackup
AS
BEGIN
DECLARE @BackupFile NVARCHAR(500)
DECLARE @TestDBName NVARCHAR(128)
DECLARE @SQL NVARCHAR(MAX)
-- 获取最新的完整备份
SELECT TOP 1 @BackupFile = physical_device_name
FROM msdb.dbo.backupset b
JOIN msdb.dbo.backupmediafamily m ON b.media_set_id = m.media_set_id
WHERE b.type = 'D' AND b.database_name = 'YourDB'
ORDER BY b.backup_finish_date DESC
-- 创建测试数据库名
SET @TestDBName = 'YourDB_Validation_' + CONVERT(NVARCHAR, GETDATE(), 112)
-- 恢复验证
SET @SQL = '
RESTORE DATABASE ' + @TestDBName + '
FROM DISK = ''' + @BackupFile + '''
WITH MOVE ''YourDB_Data'' TO ''D:\Test\' + @TestDBName + '.mdf'',
MOVE ''YourDB_Log'' TO ''E:\Test\' + @TestDBName + '.ldf'',
REPLACE, STATS=10
'
BEGIN TRY
EXEC sp_executesql @SQL
-- 校验完整性
SET @SQL = 'DBCC CHECKDB(' + @TestDBName + ') WITH NO_INFOMSGS'
EXEC sp_executesql @SQL
-- 清理测试数据库
SET @SQL = 'DROP DATABASE ' + @TestDBName
EXEC sp_executesql @SQL
-- 记录成功
INSERT INTO DBA_ValidationLog (DatabaseName, BackupFile, Status, ValidateDate)
VALUES ('YourDB', @BackupFile, 'SUCCESS', GETDATE())
END TRY
BEGIN CATCH
-- 记录失败
INSERT INTO DBA_ValidationLog (DatabaseName, BackupFile, Status, ErrorMsg, ValidateDate)
VALUES ('YourDB', @BackupFile, 'FAILED', ERROR_MESSAGE(), GETDATE())
-- 发送告警
EXEC msdb.dbo.sp_send_dbmail
@recipients = 'dba@company.com',
@subject = '备份验证失败',
@body = ERROR_MESSAGE()
END CATCH
END
六、监控与维护
6.1 作业监控
-- 查看作业执行历史
SELECT
sj.name AS JobName,
sh.run_date,
sh.run_time,
sh.run_duration,
CASE sh.run_status
WHEN 0 THEN 'Failed'
WHEN 1 THEN 'Succeeded'
WHEN 2 THEN 'Retry'
WHEN 3 THEN 'Cancelled'
WHEN 4 THEN 'In Progress'
END AS Status,
sh.message
FROM msdb.dbo.sysjobs sj
JOIN msdb.dbo.sysjobhistory sh ON sj.job_id = sh.job_id
WHERE sh.run_date >= CONVERT(INT, CONVERT(VARCHAR, DATEADD(day, -7, GETDATE()), 112))
ORDER BY sh.run_date DESC, sh.run_time DESC
-- 查看作业失败统计
SELECT
sj.name,
COUNT(*) AS FailureCount,
MAX(sh.run_date) AS LastFailureDate
FROM msdb.dbo.sysjobs sj
JOIN msdb.dbo.sysjobhistory sh ON sj.job_id = sh.job_id
WHERE sh.run_status = 0 -- Failed
AND sh.run_date >= CONVERT(INT, CONVERT(VARCHAR, DATEADD(day, -30, GETDATE()), 112))
GROUP BY sj.name
ORDER BY FailureCount DESC
-- 检查长时间运行的作业
SELECT
sj.name,
sh.run_date,
sh.run_time,
sh.run_duration,
sh.run_duration/10000 AS Hours,
(sh.run_duration%10000)/100 AS Minutes,
(sh.run_duration%100) AS Seconds
FROM msdb.dbo.sysjobs sj
JOIN msdb.dbo.sysjobhistory sh ON sj.job_id = sh.job_id
WHERE sh.run_status = 1 -- Succeeded
AND sh.run_duration > 360000 -- 超过1小时(3600秒)
ORDER BY sh.run_duration DESC
6.2 自动化运维检查清单
每日自动化:
- 数据库备份验证作业
- 磁盘空间检查与告警
- 失败作业重试机制
- 数据库完整性检查(CHECKDB)
每周自动化:
- 索引维护(重建/重组)
- 统计信息更新
- 备份文件清理
- 错误日志轮转
每月自动化:
- 备份恢复演练
- 性能基线对比
- 过期历史数据清理
- 安全审计报告
七、核心总结
| 知识点 | 核心要点 |
|---|---|
| SQL Agent 作业 | 定时执行 T-SQL/PowerShell/CmdExec 任务 |
| 步骤控制 | on_success_action / on_fail_action 控制流程 |
| 调度配置 | 频率、间隔、起止时间灵活配置 |
| 操作员 | 邮件/寻呼通知接收人 |
| 警报 | 基于错误号/严重级/性能条件的自动告警 |
| 多服务器管理 | 主服务器统一分发作业到目标服务器 |
| 数据库邮件 | 必须配置才能发送告警邮件 |
一句话记住本期内容:
SQL Agent 是 DBA 的自动化引擎——作业定任务,调度定时间,操作员定通知,多服务器定范围,把重复劳动交给机器。
动手练习
-- 场景:你需要管理 20 台 SQL Server,需要每天执行以下任务:
-- - 凌晨 2:00 完整备份所有用户数据库
-- - 凌晨 3:00 更新统计信息
-- - 每小时检查死锁(错误号 1205)
-- - 磁盘空间低于 10% 时自动告警
-- 问题1:如何实现统一管理 20 台服务器的作业?
-- 问题2:如何确保备份失败时立即通知 DBA?
-- 问题3:如何避免作业日志无限增长?
点击查看参考答案
问题1:配置多服务器管理。设置一台主服务器(MSX),其他 20 台作为目标服务器(TSX),在主服务器上创建作业并分发到所有目标服务器。
问题2:
-- 在作业中配置通知
EXEC dbo.sp_update_job
@job_name = N'DailyBackup',
@notify_level_email = 2, -- 失败时发送邮件
@notify_email_operator_name = N'DBA_Team';
问题3:设置作业历史保留期限
-- 设置只保留7天历史
EXEC msdb.dbo.sp_set_sqlagent_properties
@jobhistory_max_rows = 10000,
@jobhistory_max_rows_per_job = 100;
下一期预告
性能监控与调优——DMV 与性能计数器的实战应用
- 核心 DMV 查询模板
- 性能计数器解读
- 基线建立与异常检测
- 等待统计深度分析
- 自动化性能报告
📌 本文代码适用于 SQL Server 2016-2022。多服务器管理需要所有服务器能互相通信且使用相同的 SQL Agent 账户权限。
管理系列持续更新中,点击关注不错过第六期。