SQL Server Agent e scheduler-ul integrat. Fiecare backup care rulează la 2 dimineața, fiecare job de mentenanță care reconstruiește indexurile duminică seara, fiecare raport orar care ajunge la financiar — e Agent, ticăind în tăcere.
Până când ceva eșuează în tăcere, alerta pe email nu pleacă pentru că operatorul nu a fost configurat și afli miercurea când Financiarul întreabă „hei, unde e raportul de luni?”
Lecția asta e despre cum construiești job-uri Agent care chiar îți spun când se strică. Plus echivalentul Azure SQL Database, pentru că Azure nu are Agent propriu-zis.
Ce e SQL Agent
Un serviciu Windows separat (SQLSERVERAGENT) care rulează alături de SQL Server. Job-ul lui: să execute job-uri programate. Agent trăiește în interiorul msdb, motiv pentru care lecția 3 a spus că msdb e „unde trăiește SQL Agent.”
Job-urile au:
- Pași (steps) — unul sau mai mulți. Fiecare e T-SQL, un script PowerShell, o comandă OS, un pachet SSIS sau alte câteva tipuri.
- Programări (schedules) — când rulează. Pattern-uri stil cron.
- Alerte — ce să facă la eșec (email către operator, rulează alt job etc.).
- Owner — de obicei
sa; rularea sub un cont impersonat e posibilă.
Crearea unui job simplu
Prin T-SQL (UI-ul face același lucru în spate):
USE msdb;
GO
EXEC sp_add_job
@job_name = N'Runehold - Nightly Full Backup',
@enabled = 1,
@description = N'Takes a compressed FULL backup of Runehold to D:\Backups';
EXEC sp_add_jobstep
@job_name = N'Runehold - Nightly Full Backup',
@step_name = N'Backup',
@subsystem = N'TSQL',
@command = N'
BACKUP DATABASE Runehold
TO DISK = N''D:\Backups\Runehold_FULL.bak''
WITH FORMAT, INIT, COMPRESSION, CHECKSUM;
',
@database_name = N'master';
EXEC sp_add_schedule
@schedule_name = N'Nightly 02:00',
@freq_type = 4, -- zilnic
@freq_interval = 1, -- în fiecare zi
@active_start_time = 020000; -- 02:00:00
EXEC sp_attach_schedule
@job_name = N'Runehold - Nightly Full Backup',
@schedule_name = N'Nightly 02:00';
EXEC sp_add_jobserver
@job_name = N'Runehold - Nightly Full Backup',
@server_name = @@SERVERNAME;
Urât. UI-ul SSMS e de obicei mai puțin dureros pentru job-uri unice. Scripturile ca cel de mai sus sunt pentru a deploia job-uri în mai multe medii.
Alerte și operatori
By default, un job eșuat scrie o intrare în Windows Event Log și… atât. Nu știe nimeni. Ca să fii notificat:
Configurează Database Mail
SQL Server are nevoie de un profil de email:
EXEC msdb.dbo.sysmail_add_account_sp
@account_name = N'Runehold SMTP',
@description = N'SMTP account for ops notifications',
@email_address = N'ops@runehold.example',
@display_name = N'Runehold Ops',
@mailserver_name = N'smtp.runehold.example',
@port = 587,
@enable_ssl = 1,
@username = N'ops@runehold.example',
@password = N'secret';
EXEC msdb.dbo.sysmail_add_profile_sp @profile_name = N'Default';
EXEC msdb.dbo.sysmail_add_profileaccount_sp
@profile_name = N'Default',
@account_name = N'Runehold SMTP',
@sequence_number = 1;
Definește un operator
EXEC msdb.dbo.sp_add_operator
@name = N'DBA Team',
@enabled = 1,
@email_address = N'dba@runehold.example';
Spune-i job-ului să notifice la eșec
EXEC msdb.dbo.sp_update_job
@job_name = N'Runehold - Nightly Full Backup',
@notify_level_email = 2, -- 2 = la eșec
@notify_email_operator_name = N'DBA Team';
Valorile notify_level_email: 0 (niciodată), 1 (la succes), 2 (la eșec), 3 (la finalizare).
Setează asta mereu pe 2 pentru fiecare job. „Job-ul a rulat, dar a eșuat în tăcere” e cel mai comun dezastru operațional evitabil.
Job-uri cu mai mulți pași
Un job poate avea mai mulți pași. Fiecare pas poate ramifica pe succes sau eșec:
- La succes → mergi la pasul următor, ieși cu succes sau sari la un pas specific.
- La eșec → ieși cu eșec, ieși cu succes (!), sau sari în altă parte.
Ramura „ieși cu succes la eșec” e utilă când un pas e opțional. De exemplu: „update stats; dacă eșuează dintr-un motiv (locked), sari peste; dacă eșuează din alt motiv, abandonează.”
În general: ține pașii atomici și lasă eșecurile să se propage. Înghițirea silențioasă a eșecurilor e cum se întâmplă dezastrele tăcute.
Citirea istoricului de job
UI-ul arată rulările recente pentru fiecare job. Programatic:
SELECT TOP (20)
j.name AS job_name,
s.step_id,
s.step_name,
CASE h.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,
CONVERT(VARCHAR, msdb.dbo.agent_datetime(h.run_date, h.run_time), 120) AS run_time,
h.run_duration,
h.message
FROM msdb.dbo.sysjobhistory AS h
JOIN msdb.dbo.sysjobs AS j ON j.job_id = h.job_id
LEFT JOIN msdb.dbo.sysjobsteps AS s
ON s.job_id = h.job_id AND s.step_id = h.step_id
ORDER BY h.run_date DESC, h.run_time DESC;
Arată fiecare pas recent al fiecărui job cu rezultatul lui. Rulează asta pe un server necunoscut ca să vezi rapid ce a tot eșuat.
Capcana token-expansion
Textul comenzii unui pas poate folosi token-uri Agent precum $(ESCAPE_SQUOTE(JOBNAME)). De la SQL 2005, SQL Server cere „escape macros” în jurul token-urilor pentru a preveni injection. O comandă scrisă fără ele va eșua cu „Unable to parse token” sau ceva similar. Dacă moștenești job-uri vechi, fii atent la asta când le editezi.
Job-urile Agent pe care toată lumea ar trebui să le aibă
Setul minim pentru orice SQL Server de producție:
- Backup FULL nocturn (sau DIFF + FULL zilnic).
- Log backups frecvente (la fiecare 15-30 min dacă ești pe FULL recovery).
IndexOptimizesăptămânal — scriptul lui Ola, acoperit în lecția 35.DatabaseIntegrityChecksăptămânal — wrapper-ul lui Ola pesteDBCC CHECKDB.- Monitorizare / heartbeat — un job care eșuează dacă ceva e în neregulă cu serverul, emițând o alertă.
- Curățare istoric job-uri Agent — Agent ține istoricul pentru totdeauna dacă nu-l cureți. După un an sau doi pe un server ocupat,
msdbdevine uriaș.
Scriptul de instalare Ola Hallengren (lecția 35) creează #3 și #4 pentru tine.
Agent pe Azure SQL Database
Lui Azure SQL Database îi lipsește Agent. Echivalentul e Elastic Jobs: o resursă Azure separată care rulează T-SQL pe un orar pe baze de date țintă.
- Creezi o resursă Elastic Job Agent în Azure.
- Bazele de date țintă pot fi individuale sau un întreg server / pool.
- Job-urile sunt definite în T-SQL și au programare configurabilă.
- Monitorizare prin Azure Portal sau DMV-uri.
Azure SQL Managed Instance are SQL Agent, deci dacă migrezi de pe on-prem și ai nevoie de Agent, MI e calea drop-in.
Pentru Runehold, care rulează pe Azure SQL MI, folosim Agent ca pe on-prem. Dacă am fi fost pe Azure SQL DB, am fi reconstruit job-urile de mentenanță în Elastic Jobs.
Monitorizarea Agent din afara SQL
Pentru firme serioase, trimiterea rezultatelor de job Agent către un sistem central de monitorizare (Datadog, Grafana, Azure Monitor, Prometheus) nu e opțională. Unealta DBA preferată e:
- Un script dedicat care rulează la fiecare N minute, interoghează
msdb.dbo.sysjobhistoryși emite o metrică per job per rezultat. - PowerShell sau un agent sidecar face polling-ul.
Odată ce asta există, „un backup job care n-a rulat” devine o alertă în canalele tale normale de ops, nu o surpriză miercurea.
Rulează asta pe propria mașină
USE msdb;
GO
-- 1. Vezi ce job-uri există
SELECT name, enabled, date_created, date_modified FROM msdb.dbo.sysjobs;
-- 2. Vezi istoricul recent
SELECT TOP (10)
j.name AS job_name,
CASE h.run_status WHEN 0 THEN 'Failed' WHEN 1 THEN 'Succeeded' ELSE CAST(h.run_status AS VARCHAR) END AS status,
CONVERT(VARCHAR, msdb.dbo.agent_datetime(h.run_date, h.run_time), 120) AS run_time,
h.message
FROM msdb.dbo.sysjobhistory AS h
JOIN msdb.dbo.sysjobs AS j ON j.job_id = h.job_id
ORDER BY h.run_date DESC, h.run_time DESC;
-- 3. Creează un job simplu de smoke-test
EXEC sp_add_job @job_name = N'Smoke Test';
EXEC sp_add_jobstep @job_name = N'Smoke Test',
@step_name = N'Check',
@subsystem = N'TSQL',
@command = N'SELECT GETDATE() AS ping;',
@database_name = N'master';
EXEC sp_add_schedule @schedule_name = N'Every 5 min',
@freq_type = 4, @freq_interval = 1,
@freq_subday_type = 4, @freq_subday_interval = 5;
EXEC sp_attach_schedule @job_name = N'Smoke Test', @schedule_name = N'Every 5 min';
EXEC sp_add_jobserver @job_name = N'Smoke Test', @server_name = @@SERVERNAME;
-- 4. Rulează-l acum
EXEC sp_start_job @job_name = N'Smoke Test';
-- 5. Verifică istoricul
SELECT TOP (5) * FROM msdb.dbo.sysjobhistory
WHERE job_id = (SELECT job_id FROM msdb.dbo.sysjobs WHERE name = N'Smoke Test')
ORDER BY run_date DESC, run_time DESC;
-- 6. Curăță
EXEC sp_delete_job @job_name = N'Smoke Test';
Agent e neglamoros. Rulează indiferent dacă te uită cineva sau nu. Asta îl face fie cel mai bun prieten, fie cel mai tăcut dușman, în funcție de cum ai cablat alertele.
Urmează: securitate — login-uri, useri, role-uri și de ce rolul public e capcana în care cad cei mai mulți.