Kaydı kimin sildiğiyle ilgili bilgileri Sil tetikleyicisine aktarma


11

Bir denetim izi ayarlarken, bir tablodaki kayıtları kimin güncellediğini veya eklediğini izleme konusunda sorunum yok, ancak kayıtları kimin sildiğini izlemek daha sorunlu görünüyor.

Ekler / Güncellemeler'i "UpdatedBy" Ekle / Güncelle alanına dahil ederek takip edebilirim. Bu INSERT / UPDATE tetikleyicisinin üzerinden "UpdatedBy" alanına erişmesini sağlar inserted.UpdatedBy. Ancak Sil tetikleyicisi ile hiçbir veri eklenmez / güncellenmez. Kaydı kimin sildiğini bilecek şekilde Sil tetikleyicisine bilgi aktarmanın bir yolu var mı?

İşte bir Ekle / Güncelle tetikleyicisi

ALTER TRIGGER [dbo].[trg_MyTable_InsertUpdate] 
ON [dbo].[MyTable]
FOR INSERT, UPDATE
AS  

INSERT INTO AuditTable (IdOfRecordedAffected, UserWhoMadeChanges) 
VALUES (inserted.ID, inserted.LastUpdatedBy)
FROM inserted 

SQL Server 2012'yi kullanma


1
Bu cevaba bakınız . SUSER_SNAME()kaydı kimin sildiğinin anahtarıdır.
Kin Shah

1
Teşekkürler Kin, ancak SUSER_SNAME()tek bir kullanıcının tüm uygulama için veritabanı iletişimi için kullanılabileceği bir web uygulaması gibi bir durumda çalışacağını düşünmüyorum .
webworm

1
Bir web uygulaması çağırdığınızı söylemediniz.
Kin Shah

Üzgünüz Kin, uygulama türüne daha özel olmalıydım.
webworm

Yanıtlar:


10

Kaydı kimin sildiğini bilecek şekilde Sil tetikleyicisine bilgi aktarmanın bir yolu var mı?

Evet: çok serin (ve az kullanılan özellik) kullanarak denir CONTEXT_INFO. Esasen tüm kapsamlarda bulunan ve işlemlere bağlı olmayan oturum hafızasıdır. Bilgi (herhangi bir bilgi - iyi, sınırlı alana uyan herhangi bir bilgi) tetikleyicilere ve alt proc / EXEC çağrıları arasında ileri geri iletmek için kullanılabilir. Ve daha önce aynı durum için kullandım.

Nasıl çalıştığını görmek için aşağıdakileri test edin. Ben CHAR(128)önce dönüştürmek dikkat edin CONVERT(VARBINARY(128), ... Bu daha kolay geri dönüştürme yapmak için kuvvet boş-dolgu etmektir VARCHARarasında dışarı alırken CONTEXT_INFO()beri VARBINARY(128)birlikte sağ yastıklı olduğunu 0x00s.

SELECT CONTEXT_INFO();
-- Initially = NULL

DECLARE @EncodedUser VARBINARY(128);
SET @EncodedUser = CONVERT(VARBINARY(128),
                            CONVERT(CHAR(128), 'I deleted ALL your records! HA HA!')
                          );
SET CONTEXT_INFO @EncodedUser;

SELECT CONTEXT_INFO() AS [RawContextInfo],
       RTRIM(CONVERT(VARCHAR(128), CONTEXT_INFO())) AS [DecodedUser];

Sonuçlar:

0x492064656C6574656420414C4C20796F7572207265636F7264732120484120484121202020202020...
I deleted ALL your records! HA HA!

HEPSİNİ BİR ARAYA KOY:

  1. Uygulama, Kaydı silen KullanıcıAdı'na (veya her neyse) geçen "Sil" saklı yordamını çağırmalıdır. Zaten Ekle ve Güncelle işlemlerini izliyormuş gibi göründüğünden, bunun zaten kullanılan model olduğunu varsayıyorum.

  2. "Sil" saklı yordamı şunları yapar:

    DECLARE @EncodedUser VARBINARY(128);
    SET @EncodedUser = CONVERT(VARBINARY(128),
                                CONVERT(CHAR(128), @UserName)
                              );
    SET CONTEXT_INFO @EncodedUser;
    
    -- DELETE STUFF HERE
    
  3. Denetim tetikleyicisi şunları yapar:

    -- Set the INT value in LEFT (currently 50) to the max size of [UserWhoMadeChanges]
    INSERT INTO AuditTable (IdOfRecordedAffected, UserWhoMadeChanges) 
       SELECT del.ID, COALESCE(
                         LEFT(RTRIM(CONVERT(VARCHAR(128), CONTEXT_INFO())), 50),
                         '<unknown>')
       FROM DELETED del;
    
  4. @SeanGallardy'nin bir yorumda işaret ettiği gibi, bu tabloda kayıtların silinmesine ilişkin diğer prosedürler ve / veya geçici sorgular nedeniyle aşağıdakilerden birinin mümkün olabileceğini lütfen unutmayın:

    • CONTEXT_INFOayarlanmadı ve hala NULL:

      Bu nedenle , değeri varsayılan INSERT INTO AuditTableolarak kullanmak için yukarıdakileri güncelledim COALESCE. Veya bir varsayılan istemiyorsanız ve bir ad gerektiriyorsanız, aşağıdakine benzer bir şey yapabilirsiniz:

      DECLARE @UserName VARCHAR(50); -- set to the size of AuditTable.[UserWhoMadeChanges]
      SET @UserName = LEFT(RTRIM(CONVERT(VARCHAR(128), CONTEXT_INFO())), 50);
      
      IF (@UserName IS NULL)
      BEGIN
         ROLLBACK TRAN; -- cancel the DELETE operation
         RAISERROR('Please set UserName via "SET CONTEXT_INFO.." and try again.', 16 ,1);
      END;
      
      -- use @UserName in the INSERT...SELECT
      
    • CONTEXT_INFO, geçerli bir KullanıcıAdı olmayan bir değere ayarlanmıştır ve bu nedenle AuditTable.[UserWhoMadeChanges]alanın boyutunu aşabilir :

      Bu nedenle ben bir katma LEFTdışarı yakaladı ne olursa olsun sağlamak için işlevini CONTEXT_INFOkırmak olmaz INSERT. Kodda belirtildiği gibi, sadece alanın 50gerçek boyutuna ayarlamanız gerekir UserWhoMadeChanges.


SQL SERVER 2016 VE YENİ İÇİN GÜNCELLEME

SQL Server 2016, bu oturum başına belleğin geliştirilmiş bir sürümünü ekledi: Oturum Bağlamı. Yeni Oturum Bağlamı, temelde Anahtar-Değer çiftlerinin bir karma tablosu olup, "Anahtar" tip sysname(yani NVARCHAR(128)) ve "Değer" varlıktır SQL_VARIANT. Anlamı:

  1. Artık diğer kullanımlar ile çatışması daha düşük olan değerlerin ayrılması
  2. Değeri geri alırken artık garip davranışlar hakkında endişelenmenize gerek olmayan çeşitli türleri saklayabilirsiniz CONTEXT_INFO()(ayrıntılar için lütfen gönderime bakın: CONTEXT_INFO () SET CONTEXT_INFO tarafından ayarlanan Tam Değeri neden döndürmüyor? )
  3. Çok daha fazla alan elde edersiniz : "Değer" başına maksimum 8000 bayt, tüm tuşlarda toplam 256 kb'ye kadar (maks. 128 bayt ile karşılaştırıldığında CONTEXT_INFO)

Ayrıntılar için lütfen aşağıdaki dokümantasyon sayfalarına bakın:


Bu yaklaşımla ilgili sorun ÇOK uçucu olmasıdır. Herhangi bir oturum bunu ayarlayabilir, böylece önceden ayarlanmış herhangi bir öğenin üzerine yazabilir. Uygulamanızı gerçekten kırmak mı istiyorsunuz? beklediğiniz şeyin üzerine tek bir geliştirici ekleyin. Bunu kullanmamanızı ve mimari değişiklik gerektirebilecek standart bir yaklaşıma sahip olmamayı şiddetle tavsiye ederim. Aksi takdirde ateşle oynuyorsun.
Sean Gallardy

@SeanGallardy Lütfen bunun gerçek bir örneğini verebilir misiniz? Oturum == @@SPID. Bu PER-Oturum / Bağlantı hafızasıdır. Bir oturum başka bir oturumun bağlam bilgilerinin üzerine yazamaz. Ve oturum oturumu kapattığında değer kaybolur. "Önceden ayarlanmış bir öğe" diye bir şey yoktur.
Solomon Rutzky

1
"Başka bir oturum" demedim, oturum kapsamındaki herhangi bir nesnenin bunu yapabileceğini söyledim. Yani, bir geliştirici kendi "içeriksel" bilgilerini tutmak için bir sproc yazar ve şimdi sizinki üzerine yazılır. Bu aynı desen kullanılan başa çıkmak zorunda kaldım bir uygulama vardı, bunun olduğunu izledim ... İK yazılımı oldu. Size "mutlu" bir "hata" nedeniyle devs tarafından bir "hata" nedeniyle ne kadar mutlu olduğunu söyleyeyim yeni bir SP yazma oturumu için bağlam bilgileri yanlışlıkla olması gerekiyordu ne güncellenen. Sadece bir örnek vermek gerekirse, bu yöntemi neden kullanmamaya tanık oldum.
Sean Gallardy

@SeanGallardy Tamam, bu noktayı açıklığa kavuşturduğunuz için teşekkürler. Ama yine de sadece kısmen geçerli bir nokta. Bu durumun gerçekleşmesi için, bu "diğer" proc'un bu durumun içinde çağrılması gerekir. Veya, bu tablodan silinip tetikleyiciyi tekmeleyebilecek başka bir proctan bahsediyorsanız, bu test edilebilen bir şeydir. Bu, hesaba katılması gereken bir şeydir (tıpkı tüm çok iş parçacıklı uygulamalarda olduğu gibi) ve bu tekniği kullanmamanın bir nedeni değildir. Ve bunu yapmak için küçük bir güncelleme yapacağım. Bu olasılığı artırdığın için teşekkürler.
Solomon Rutzky

2
Ben güvenlik sonrası düşünce olarak ana sorun olduğunu söylüyorum ve bu onu çözmek için bir araç değil. Memo yapıları veya uygulamayı bozmayan diğer kullanımlar, hiçbir sorunum olmadığından emin olun. Kesinlikle kullanmamak için bir neden. YMMV ama güvenlik gibi önemli bir şey için asla bu kadar uçucu ve yapılandırılmamış bir şey kullanmam. Güvenlik için herhangi bir paylaşılan kullanıcı yazılabilir depolama alanı kullanmak genel olarak korkunç bir fikirdir. Uygun tasarım, çoğunlukla bu gibi şeylere olan ihtiyacı ortadan kaldıracaktır.
Sean Gallardy

5

Bir uygulama düzeyi yerine SQL sunucu kullanıcı kimliğini kaydetmek istemiyorsanız, bu şekilde yapamazsınız.

SilinmişBy adlı bir sütuna sahip olarak ve gerektiğinde ayarlayarak yumuşak bir silme yapabilirsiniz, o zaman güncelleme tetikleyiciniz gerçek silme işlemini yapabilir (veya kaydı arşivleyebilir, genellikle mümkün ve yasal olduğunda sert silmelerden kaçınırım) ve denetim izinizi güncelleyebilirsiniz . Bu şekilde yapılacak silmeleri zorlamak için on deletebir hata oluşturan bir tetikleyici tanımlayın . Fiziksel tablonuza sütun eklemek istemiyorsanız, sütunu ekleyen bir görünüm tanımlayabilir instead ofve temel tabloyu güncellemek için tetikleyiciler tanımlayabilirsiniz , ancak bu aşırı olabilir.


Senin değinmek istediğin noktayı anlıyorum. Gerçekten uygulama düzeyinde kullanıcı günlüğü arıyor olacaktı.
webworm

David, aslında tetikleyicilere bilgi iletebilirsiniz. Lütfen detaylar için cevabım bakınız :).
Solomon Rutzky

Burada iyi bir öneri, bu rotayı çok seviyorum. Gerçek silme işlemini tetikleyen adımda Kim'i yakalayarak iki kuşu öldürür. Bu sütun, bu tablodaki her kayıt için NULL olacak, SQL Server SPARSEsütun iyi bir kullanım gibi görünüyor ?
Airn5475

2

Kaydı kimin sildiğini bilecek şekilde Sil tetikleyicisine bilgi aktarmanın bir yolu var mı?

Evet, görünüşe göre iki yol var ;-). Burada diğer cevabımdaCONTEXT_INFO önerdiğim gibi kullanma hakkında herhangi bir rezervasyon varsa , sadece diğer kod / süreçlerden daha temiz bir fonksiyonel ayrımı olan başka bir yol düşündüm: yerel bir geçici tablo kullanın.

Geçici tablo adı, aynı oturumda çalıştırılabilecek diğer kodlardan ayrı tutulmasına yardımcı olacağı için silinecek tablo adını içermelidir. Çizgileri boyunca bir şey:
#<TableName>DeleteAudit

Yerel geçici tablonun bir yararı CONTEXT_INFO, başka bir proctaki - bu belirli "Sil" procundan bir şekilde çağrı yapan - sadece aynı geçici tablo adını yanlış kullanması durumunda, alt işlemin a) yeni bir yerel oluşturmasıdır. bu ilk geçici tablodan ayrı olacak istenen adın geçici tablosu (aynı ada sahip olmasına rağmen) ve b) alt işlemdeki yeni yerel geçici tabloya karşı herhangi bir DML ifadesi, Burada üst süreçte oluşturulan yerel geçici tablo, dolayısıyla verilerin üzerine yazılmaz. Tabii ki, ilk önce bir verme olmadan bu geçici tablo adı karşı DML deyimi aynı adı taşıyan TABLO OLUŞTURMAK bir alt işlemi sorunları varsa, o zaman bu DML ifadeleri olacak bu tablodaki verileri etkiler. AMA, bu noktada gerçektenburada kenar-casey, üst üste binme olasılığından daha da fazla CONTEXT_INFO(evet, bunun olduğunu biliyorum, bu yüzden "asla olmayacak" yerine "edge-case" diyorum).

  1. Uygulama, Kaydı silen KullanıcıAdı'na (veya her neyse) geçen "Sil" saklı yordamını çağırmalıdır. Zaten Ekle ve Güncelle işlemlerini izliyormuş gibi göründüğünden, bunun zaten kullanılan model olduğunu varsayıyorum.

  2. "Sil" saklı yordamı şunları yapar:

    CREATE TABLE #MyTableDeleteAudit (UserName VARCHAR(50));
    INSERT INTO #MyTableDeleteAudit (UserName) VALUES (@UserName);
    
    -- DELETE STUFF HERE
    
  3. Denetim tetikleyicisi şunları yapar:

    -- Set the datatype and length to be the same as the [UserWhoMadeChanges] field
    DECLARE @UserName VARCHAR(50);
    IF (OBJECT_ID(N'tempdb..#TriggerTestDeleteAudit') IS NOT NULL)
    BEGIN
       SELECT @UserName = UserName
       FROM #TriggerTestDeleteAudit;
    END;
    
    -- catch the following conditions: missing table, no rows in table, or empty row
    IF (@UserName IS NULL OR @UserName NOT LIKE '%[a-z]%')
    BEGIN
      /* -- uncomment if undefined UserName == badness
       ROLLBACK TRAN; -- cancel the DELETE operation
       RAISERROR('Please set UserName via #TriggerTestDeleteAudit and try again.', 16 ,1);
       RETURN; -- exit
      */
      /* -- uncomment if undefined UserName gets default value
       SET @UserName = '<unknown>';
      */
    END;
    
    INSERT INTO AuditTable (IdOfRecordedAffected, UserWhoMadeChanges) 
       SELECT del.ID, @UserName
       FROM DELETED del;
    

    Bu kodu bir tetikleyicide test ettim ve beklendiği gibi çalışıyor.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.