Pekerjaan SQL Server Agent dan Ketersediaan Grup

37

Saya mencari praktik terbaik dalam menangani pekerjaan Agen SQL Server terjadwal di grup ketersediaan SQL Server 2012. Mungkin saya melewatkan sesuatu, namun pada kondisi saat ini saya merasa bahwa SQL Server Agent tidak benar-benar terintegrasi dengan fitur SQL2012 yang hebat ini.

Bagaimana saya bisa membuat pekerjaan agen SQL terjadwal menyadari adanya switch node? Sebagai contoh, saya memiliki pekerjaan yang berjalan pada node primer yang memuat data setiap jam. Sekarang jika primary turun, bagaimana saya bisa mengaktifkan pekerjaan pada secondary yang sekarang menjadi primary?

Jika saya menjadwalkan pekerjaan selalu pada sekunder, itu gagal karena kemudian sekunder adalah read-only.

nojetlag
sumber

Jawaban:

40

Dalam pekerjaan SQL Server Agent Anda, miliki beberapa logika kondisional untuk menguji jika instance saat ini melayani peran tertentu yang Anda cari pada grup ketersediaan Anda:

if (select
        ars.role_desc
    from sys.dm_hadr_availability_replica_states ars
    inner join sys.availability_groups ag
    on ars.group_id = ag.group_id
    where ag.name = 'YourAvailabilityGroupName'
    and ars.is_local = 1) = 'PRIMARY'
begin
    -- this server is the primary replica, do something here
end
else
begin
    -- this server is not the primary replica, (optional) do something here
end

Yang dilakukan adalah menarik peran replika lokal saat ini, dan jika ada dalam PRIMARYperan itu, Anda dapat melakukan apa pun yang perlu dilakukan pekerjaan Anda jika itu adalah replika utama. The ELSEblok opsional, tapi itu untuk menangani logika mungkin jika replika lokal Anda tidak primer.

Tentu saja, ubah 'YourAvailabilityGroupName'kueri di atas ke nama grup ketersediaan aktual Anda.

Jangan bingung antara grup ketersediaan dengan instance cluster failover. Apakah instance adalah replika primer atau sekunder untuk grup ketersediaan yang diberikan tidak mempengaruhi objek tingkat server, seperti pekerjaan SQL Server Agent dan sebagainya.

Thomas Stringer
sumber
14

Daripada melakukan ini berdasarkan per pekerjaan (memeriksa setiap pekerjaan untuk status server sebelum memutuskan untuk melanjutkan), saya telah menciptakan pekerjaan yang berjalan di kedua server untuk memeriksa untuk melihat keadaan server itu.

  • Jika utama, maka aktifkan pekerjaan apa pun yang memiliki langkah menargetkan database di AG.
  • Jika server sekunder, nonaktifkan pekerjaan apa pun yang menargetkan database di AG.

Pendekatan ini menyediakan sejumlah hal

  • ini bekerja pada server di mana tidak ada database di AG (atau campuran Db masuk / keluar dari AG)
  • siapa pun dapat membuat pekerjaan baru dan tidak perlu khawatir tentang apakah db berada di AG (walaupun mereka harus ingat untuk menambahkan pekerjaan ke server lain)
  • Mengizinkan setiap pekerjaan memiliki email kegagalan yang tetap berguna (semua pekerjaan Anda memiliki email kegagalan, bukan?)
  • Saat melihat riwayat pekerjaan, Anda benar-benar bisa melihat apakah pekerjaan itu benar-benar berjalan dan melakukan sesuatu (ini yang utama), daripada melihat daftar panjang kesuksesan yang sebenarnya tidak menjalankan apa pun (pada tingkat sekunder)

skrip memeriksa database di bidang di bawah ini jika database ini berada dalam Grup yang Tersedia, skrip akan mengambil tindakan

Proc ini dijalankan setiap 15 menit pada setiap server. (memiliki bonus tambahan menambahkan komentar untuk memberi tahu orang-orang mengapa pekerjaan itu dinonaktifkan)

/*
    This proc goes through all SQL Server agent jobs and finds any that refer to a database taking part in the availability Group 
    It will then enable/disable the job dependant on whether the server is the primary replica or not   
        Primary Replica = enable job
    It will also add a comment to the job indicating the job was updated by this proc
*/
CREATE PROCEDURE dbo.sp_HADRAgentJobFailover (@AGname varchar(200) = 'AG01' )
AS 

DECLARE @SQL NVARCHAR(MAX)

;WITH DBinAG AS (  -- This finds all databases in the AG and determines whether Jobs targeting these DB's should be turned on (which is the same for all db's in the AG)
SELECT  distinct
        runJobs = CASE WHEN role_desc = 'Primary' THEN 1 ELSE 0 END   --If this is the primary, then yes we want to run the jobs
        ,dbname = db.name
        ,JobDescription = CASE WHEN hars.role_desc = 'Primary'  -- Add the reason for the changing the state to the Jobs description
                THEN '~~~ [Enabled] using automated process (DBA_tools.dbo.sp_HADRAgentJobFailover) looking for jobs running against Primary Replica AG ~~~ '
                ELSE '~~~ [Diabled] using Automated process (DBA_tools.dbo.sp_HADRAgentJobFailover) because the job cant run on READ-ONLY Replica AG~~~ ' END 
FROM sys.dm_hadr_availability_replica_states hars
INNER JOIN sys.availability_groups ag ON ag.group_id = hars.group_id
INNER JOIN sys.Databases db ON  db.replica_id = hars.replica_id
WHERE is_local = 1
AND ag.Name = @AGname
) 

SELECT @SQL = (
SELECT DISTINCT N'exec msdb..sp_update_job @job_name = ''' + j.name + ''', @enabled = ' + CAST(d.runJobs AS VARCHAR) 
                + ',@description = ''' 
                + CASE WHEN j.description = 'No description available.' THEN JobDescription -- if there is no description just add our JobDescription
                       WHEN PATINDEX('%~~~%~~~',j.description) = 0 THEN j.description + '    ' + JobDescription  -- If our JobDescription is NOT there, add it
                       WHEN PATINDEX('%~~~%~~~',j.description) > 0 THEN SUBSTRING(j.description,1,CHARINDEX('~~~',j.description)-1) + d.JobDescription  --Replace our part of the job description with what we are doing.
                       ELSE d.JobDescription  -- Should never reach here...
                    END 
                + ''';'
FROM msdb.dbo.sysjobs j
INNER JOIN msdb.dbo.sysjobsteps s
INNER JOIN DBinAG d ON d.DbName =s.database_name     
ON j.job_id = s.job_id
WHERE j.enabled != d.runJobs   -- Ensure we only actually update the job, if it needs to change
FOR XML PATH ('')
)
PRINT REPLACE(@SQL,';',CHAR(10))
EXEC sys.sp_executesql @SQL

Ini bukan bukti bodoh, tetapi untuk muatan semalam dan pekerjaan per jam itu menyelesaikan pekerjaan.

Bahkan lebih baik daripada menjalankan prosedur ini pada jadwal, alih-alih menjalankannya sebagai respons terhadap Peringatan 1480 (peringatan perubahan peran AG).

Trubs
sumber
9

Saya menyadari dua konsep untuk mencapai ini.

Prasyarat: Berdasarkan jawaban Thomas Stringer, saya membuat dua fungsi di master db dari dua server kami:

CREATE FUNCTION [dbo].[svf_AgReplicaState](@availability_group_name sysname)
RETURNS bit
AS
BEGIN

if EXISTS(
    SELECT        ag.name
    FROM            sys.dm_hadr_availability_replica_states AS ars INNER JOIN
                             sys.availability_groups AS ag ON ars.group_id = ag.group_id
    WHERE        (ars.is_local = 1) AND (ars.role_desc = 'PRIMARY') AND (ag.name = @availability_group_name))

    RETURN 1

RETURN 0

END
GO

CREATE FUNCTION [dbo].[svf_DbReplicaState](@database_name sysname)
RETURNS bit
AS
BEGIN

IF EXISTS(
    SELECT        adc.database_name
    FROM            sys.dm_hadr_availability_replica_states AS ars INNER JOIN
                             sys.availability_databases_cluster AS adc ON ars.group_id = adc.group_id
    WHERE        (ars.is_local = 1) AND (ars.role_desc = 'PRIMARY') AND (adc.database_name = @database_name))

    RETURN 1
RETURN 0

END

GO


  1. Hentikan pekerjaan jika tidak dilakukan pada replika utama

    Untuk kasus ini, setiap pekerjaan di kedua server membutuhkan salah satu dari dua cuplikan kode berikut sebagai Langkah 1:

    Periksa dengan nama grup:

    IF master.dbo.svf_AgReplicaState('my_group_name')=0
      raiserror ('This is not the primary replica.',2,1)

    Periksa dengan nama basis data:

    IF master.dbo.svf_AgReplicaState('my_db_name')=0
      raiserror ('This is not the primary replica.',2,1)

    Jika Anda menggunakan yang kedua ini, waspadalah terhadap basis data sistem - menurut definisi mereka tidak dapat menjadi bagian dari grup ketersediaan apa pun, jadi itu akan selalu gagal bagi mereka.

    Keduanya berfungsi di luar kotak untuk pengguna admin. Untuk pengguna non-admin, Anda harus menambahkan izin tambahan, salah satunya disarankan di sini :

    GRANT VIEW SERVER STATE TO [user];
    GRANT VIEW ANY DEFINITION TO [user];

    Jika Anda menetapkan tindakan kegagalan untuk Berhenti sukses melaporkan pekerjaan pada langkah pertama ini, Anda tidak akan mendapatkan log pekerjaan penuh dengan tanda silang merah jelek, untuk pekerjaan utama mereka akan berubah menjadi tanda peringatan kuning.

    Dari pengalaman kami, ini tidak ideal. Kami awalnya mengadopsi pendekatan ini, tetapi dengan cepat kehilangan jejak mengenai menemukan pekerjaan yang sebenarnya memiliki masalah, karena semua pekerjaan replika sekunder mengacaukan catatan pekerjaan dengan pesan peringatan.

    Apa yang kemudian kami lakukan adalah:

  2. Pekerjaan proxy

    Jika Anda mengadopsi konsep ini, Anda sebenarnya harus membuat dua pekerjaan per tugas yang ingin Anda lakukan. Yang pertama adalah "pekerjaan proxy" yang memeriksa apakah itu dieksekusi pada replika utama. Jika demikian, ia memulai "pekerjaan pekerja", jika tidak, itu hanya berakhir dengan anggun tanpa mengacaukan log dengan pesan peringatan atau kesalahan.

    Meskipun saya pribadi tidak suka gagasan memiliki dua pekerjaan per tugas di setiap server, saya pikir itu pasti lebih dapat dipertahankan, dan Anda tidak perlu mengatur tindakan kegagalan dari langkah untuk Keluar dari kesuksesan pelaporan pekerjaan , yang sedikit canggung.

    Untuk pekerjaan, kami mengadopsi skema penamaan. Pekerjaan proxy baru saja dipanggil {put jobname here}. Pekerjaan pekerja disebut {put jobname here} worker. Ini memungkinkan untuk secara otomatis memulai pekerjaan pekerja dari proksi. Untuk melakukannya, saya menambahkan prosedur berikut ke kedua master dbs:

    CREATE procedure [dbo].[procStartWorkerJob](@jobId uniqueidentifier, @availabilityGroup sysname, @postfix sysname = ' worker') as
    declare @name sysname
    
    if dbo.svf_AgReplicaState(@availabilityGroup)=0
        print 'This is not the primary replica.'
    else begin
        SELECT @name = name FROM msdb.dbo.sysjobs where job_id = @jobId
    
        set @name = @name + @postfix
        if exists(select name from msdb.dbo.sysjobs where name = @name)
            exec msdb.dbo.sp_start_job @name
        else begin
            set @name = 'Job '''+@name+''' not found.'
            raiserror (@name ,2,1)
        end
    end
    GO

    Ini menggunakan svf_AgReplicaStatefungsi yang ditunjukkan di atas, Anda dapat dengan mudah mengubahnya untuk memeriksa menggunakan nama database sebagai gantinya dengan memanggil fungsi lainnya.

    Dari dalam satu-satunya langkah pekerjaan proxy, Anda menyebutnya seperti ini:

    exec procStartWorkerJob $(ESCAPE_NONE(JOBID)), '{my_group_name}'

    Ini menggunakan Token seperti yang ditunjukkan di sini dan di sini untuk mendapatkan id pekerjaan saat ini. Prosedur kemudian mendapatkan nama pekerjaan saat ini dari msdb, menambahkannya  workerdan memulai pekerjaan pekerja menggunakan sp_start_job.

    Meskipun ini masih tidak ideal, itu membuat log pekerjaan lebih rapi dan dapat dipelihara daripada opsi sebelumnya. Selain itu, Anda selalu dapat menjalankan pekerjaan proxy dengan pengguna sysadmin, jadi menambahkan izin tambahan tidak diperlukan.

takrl
sumber
3

Jika proses pemuatan data adalah kueri atau prosedur panggilan sederhana maka Anda dapat membuat pekerjaan pada kedua node dan membiarkannya menentukan apakah itu simpul utama berdasarkan pada properti Updateability dari database, sebelum menjalankan proses pemuatan data:

IF (SELECT CONVERT(sysname,DatabasePropertyEx(DB_NAME(),'Updateability'))) != 'READ_ONLY'
BEGIN

-- Data Load code goes under here

END
Yasin
sumber
1

Itu selalu lebih baik untuk membuat Langkah Kerja baru yang memeriksa apakah itu adalah Replika Primer maka semuanya baik-baik saja untuk melanjutkan pelaksanaan pekerjaan lain jika itu adalah Replika Sekunder kemudian Hentikan pekerjaan itu. Jangan gagal pekerjaan lain itu akan terus mengirimkan pemberitahuan yang tidak perlu. Alih-alih menghentikan pekerjaan sehingga dibatalkan dan tidak ada pemberitahuan yang dikirim setiap kali pekerjaan ini dieksekusi pada Replika Sekunder.

Di bawah ini adalah skrip untuk menambahkan langkah pertama untuk pekerjaan tertentu.

Catatan untuk menjalankan skrip:

  • Ganti 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX tentang' dengan Job_ID
  • Ganti 'YYYYYYYYYYYYYYYYYYYYYYYYY' dengan Job_Name
  • Jika ada beberapa Grup Ketersediaan, maka setel nama AG dalam variabel @AGNameToCheck_IfMoreThanSingleAG untuk AG yang harus diperiksa untuk negara replika.

  • Perhatikan juga bahwa skrip ini harus berfungsi dengan baik bahkan pada server yang tidak memiliki grup ketersediaan. Akan mengeksekusi hanya untuk versi SQL Server 2012 dan seterusnya.

            USE [msdb]
            GO
            EXEC msdb.dbo.sp_add_jobstep @job_id=N'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', @step_name=N'CheckForSecondaryReplica', 
                    @step_id=1, 
                    @cmdexec_success_code=0, 
                    @on_success_action=3, 
                    @on_fail_action=2, 
                    @retry_attempts=0, 
                    @retry_interval=0, 
                    @os_run_priority=0, @subsystem=N'TSQL', 
                    @command=N'
            DECLARE @AGNameToCheck_IfMoreThanSingleAG VARCHAR(100)
            SET @AGNameToCheck_IfMoreThanSingleAG = ''AGName_IfMoreThanOneAG'' -- If there are Multiple AGs, then a single server can have Primary of one AG and Secondary of other. So Job creator has to define as to which AG needs to verified before the job is automatically run on Primary.
    
            DECLARE @NumberofAGs INT
            SELECT @NumberofAGs = COUNT(group_id) FROM sys.availability_groups ags
    
    
            IF(@NumberofAGs < 2)
                IF EXISTS(Select * FROM sys.dm_hadr_availability_replica_states hars WHERE role_desc = ''Secondary'' AND hars.is_local = 1)                 
                                    EXEC msdb.dbo.sp_stop_job N''YYYYYYYYYYYYYYYYYYYYYYYYYY'' ;
                                    --RAISERROR(''This is a Secondary Replica'',16,1)
    
            IF(@NumberofAGs >= 2)
                IF EXISTS(SELECT 1 FROM sys.availability_groups WHERE name = @AGNameToCheck_IfMoreThanSingleAG)
                BEGIN
                            IF EXISTS(Select * from  sys.availability_groups ag
                                            JOIN sys.dm_hadr_availability_replica_states hars
                                                        ON ag.group_id = hars.group_id
                                                        Where role_desc = ''Secondary''
                                                        AND hars.is_local = 1
                                                        AND ag.name = @AGNameToCheck_IfMoreThanSingleAG)
                            BEGIN
                                    EXEC msdb.dbo.sp_stop_job N''YYYYYYYYYYYYYYYYYYYYYYYYYY'' ;
                                    --RAISERROR(''This is a Secondary Replica'',16,1)
                            END
                END
                ELSE
                            BEGIN
                                    RAISERROR(''The Defined AG in the Variable is not a part of this Server. Please Check!!!!!!!!!!!'',16,1)
                            END', 
                    @database_name=N'master', 
                    @flags=0
            GO
Masood Hashim
sumber
0

Cara lain adalah dengan memasukkan langkah di setiap pekerjaan, yang harus dijalankan terlebih dahulu, dengan kode berikut:

IF (SELECT ars.role_desc
    FROM sys.dm_hadr_availability_replica_states ars
    INNER JOIN sys.availability_groups ag
    ON ars.group_id = ag.group_id
    AND ars.is_local = 1) <> 'PRIMARY'
BEGIN
   --We're on the secondary node, throw an error
   THROW 50001, 'Unable to execute job on secondary node',1
END

Tetapkan langkah ini untuk melanjutkan dengan langkah berikutnya tentang kesuksesan, dan untuk berhenti dari kesuksesan pelaporan pekerjaan pada kegagalan.

Saya merasa lebih bersih untuk menambahkan langkah ekstra daripada menambahkan logika tambahan ke langkah yang ada.

KoeKk
sumber
0

Pilihan lain, yang lebih baru, adalah menggunakan master.sys.fn_hadr_is_primary_replica ('DbName'). Saya telah menemukan ini sangat membantu ketika menggunakan SQL Agent untuk melakukan pemeliharaan basis data (ditambah dengan kursor yang telah saya gunakan selama bertahun-tahun) dan juga ketika menjalankan ETL atau tugas khusus basis data lainnya. Keuntungannya adalah ia memilih basis data daripada melihat seluruh Grup Ketersediaan ... jika itu yang Anda butuhkan. Itu juga membuatnya jauh lebih mustahil bahwa perintah akan dieksekusi terhadap database yang "berada" pada primary, tetapi katakanlah failover otomatis terjadi selama pelaksanaan pekerjaan, dan sekarang pada replika sekunder. Metode di atas yang melihat replika utama lihat sekali dan jangan perbarui. Perlu diingat, ini hanya cara yang berbeda untuk mencapai hasil yang sangat mirip dan memberikan lebih banyak kontrol granular, jika Anda membutuhkannya. Juga, alasan metode ini tidak dibahas ketika pertanyaan ini diajukan adalah karena Microsoft tidak merilis fungsi ini sampai setelah SQL 2014 dirilis. Berikut adalah beberapa contoh bagaimana fungsi ini dapat digunakan:

   IF master.dbo.fn_hadr_database_is_primary_replica('Admin') = 1
    BEGIN 
        -- do whatever you were going to do in the Primary:
        PRINT 'Doing stuff in the Primary Replica';
    END
ELSE 
    BEGIN 
        -- we're not in the Primary - exit gracefully:
        PRINT 'This is not the primary replica - exiting with success';
    END

Jika Anda ingin menggunakan ini untuk pemeliharaan basis data pengguna, inilah yang saya gunakan:

/*Below evaluates all user databases in the instance and gives stubs to do work; must change to get anything other than print statements*/
declare @dbname varchar(1000)
declare @sql nvarchar(4000)

declare AllUserDatabases cursor for
    select [name] from master.sys.databases
    where database_id > 4 --this excludes all sysdbs; if all but tempdb is desired, change to <> 2
    and [state] = 0

open AllUserDatabases
fetch AllUserDatabases into @dbname

while (@@FETCH_STATUS = 0)
    begin
    --PRINT @dbname
        set @sql = '
            IF master.sys.fn_hadr_is_primary_replica(''' + @dbname + ''') = 1
                BEGIN 
                    -- do whatever you are going to do in the Primary:
                    PRINT ''Doing stuff in the Primary Replica''
                END
            ELSE 
                BEGIN 
                    -- not in the Primary - exit gracefully:
                    PRINT ''This is not the primary replica - exiting with success''
                END             
        '
        exec sp_executesql @sql
        fetch AllUserDatabases into @dbname
    end
close AllUserDatabases
deallocate AllUserDatabases

Saya harap ini tip yang bermanfaat!

SQL_Hacker
sumber
0

Saya menggunakan ini:

if (select primary_replica from sys.dm_hadr_availability_group_states) = @@SERVERNAME begin
... paste your t-sql here ...

end
aleksey vitsko
sumber