1. Tetik, ilişkisel veritabanının ACID ilkesini izliyor mu? Bir ekin işlenme şansı var mı, ancak tetikleyici başarısız mı?
Bu soru, bağlantı verdiğiniz ilgili bir soruda kısmen yanıtlanmıştır . Tetikleyici kod, söz konusu ACID ilkelerinin Atomik kısmını koruyarak, tetiklenmesine neden olan DML ifadesiyle aynı işlem bağlamında yürütülür . Tetikleme deyimi ve tetikleme kodu, bir birim olarak hem başarılı hem de başarısız olur.
ASİT özellikleri aynı zamanda herhangi bir belirgin kısıtlar (ihlal etmeyen bir durumda veritabanı bırakacaktır (tetikleyici kodu dahil) bütün işlem garanti Tutarlı ) ve her türlü kazanılabilir taahhüt etkiler (veritabanı kazasında hayatta kalacaktır Dayanıklı ).
Çevredeki (belki örtük veya otomatik taahhüt) işlem de çalıştığı sürece SERIALIZABLE
yalıtım düzeyine , İzole özelliği olan değil , otomatik olarak garanti. Diğer eşzamanlı veritabanı etkinliği, tetikleme kodunuzun doğru çalışmasını engelleyebilir. Örneğin, hesap bakiyesi, okuduktan ve güncellemeden önce başka bir oturumla değiştirilebilir - klasik bir yarış koşulu.
2. IF ve UPDATE ifadelerim garip görünüyor. Doğru [Hesap] satırını güncellemenin daha iyi bir yolu var mı?
Bağlandığınız diğer sorunun tetikleyici çözüm sunmaması için çok iyi nedenler var . Denormalize bir yapıyı senkronize tutmak için tasarlanmış tetik kodu, doğru almak ve düzgün bir şekilde test etmek için son derece zor olabilir . Uzun yıllara dayanan deneyime sahip çok gelişmiş SQL Server kullanıcıları bile bununla mücadele ediyor.
Tüm senaryolarda doğruluğu korumak ve çıkmaz kilitler gibi sorunlardan kaçınmak gibi iyi performansı sürdürmek, ekstra zorluk boyutları ekler. Tetik kodunuz sağlam olmaya yakın değildir ve yalnızca tek bir işlem değiştirilse bile her hesabın bakiyesini günceller . Tetik tabanlı bir çözümle her türlü risk ve zorluk vardır, bu da görevi bu teknoloji alanına nispeten yeni olan biri için derinden uygun hale getirir.
Bazı sorunları göstermek için, aşağıda bazı örnek kod göstereceğim. Bu titizlikle test edilmiş bir çözüm değildir (tetikleyiciler zordur!) Ve bunu bir öğrenme egzersizi dışında bir şey olarak kullanmanızı önermiyorum. Gerçek bir sistem için, tetikleyici olmayan çözümlerin önemli faydaları vardır, bu nedenle diğer sorunun cevaplarını dikkatlice gözden geçirmeli ve tetikleyici fikrinden tamamen kaçınmalısınız.
Örnek tablolar
CREATE TABLE dbo.Accounts
(
AccountID integer NOT NULL,
Balance money NOT NULL,
CONSTRAINT PK_Accounts_ID
PRIMARY KEY CLUSTERED (AccountID)
);
CREATE TABLE dbo.Transactions
(
TransactionID integer IDENTITY NOT NULL,
AccountID integer NOT NULL,
Amount money NOT NULL,
CONSTRAINT PK_Transactions_ID
PRIMARY KEY CLUSTERED (TransactionID),
CONSTRAINT FK_Accounts
FOREIGN KEY (AccountID)
REFERENCES dbo.Accounts (AccountID)
);
önleme TRUNCATE TABLE
Tetikleyiciler tarafından tetiklenmez TRUNCATE TABLE
. Aşağıdaki boş tablo yalnızca Transactions
tablonun kısaltılmasını önlemek için mevcuttur (yabancı bir anahtar tarafından başvurulması tablonun kısalmasını önler):
CREATE TABLE dbo.PreventTransactionsTruncation
(
Dummy integer NULL,
CONSTRAINT FK_Transactions
FOREIGN KEY (Dummy)
REFERENCES dbo.Transactions (TransactionID),
CONSTRAINT CHK_NoRows
CHECK (Dummy IS NULL AND Dummy IS NOT NULL)
);
Tetikleyici Tanımı
Aşağıdaki tetikleyici kodu, yalnızca gerekli hesap girişlerinin korunmasını sağlar ve SERIALIZABLE
orada semantik kullanır . Arzu edilen bir yan etki olarak, bu aynı zamanda bir satır versiyonlama izolasyon seviyesi kullanımdaysa ortaya çıkabilecek yanlış sonuçları da önler . Kod ayrıca, kaynak ifadeden hiçbir satır etkilenmediğinde tetikleyici kodunun yürütülmesini de önler. Geçici tablo ve RECOMPILE
ipucu, yanlış kardinalite tahminlerinin neden olduğu tetikleyici yürütme planı sorunlarını önlemek için kullanılır:
CREATE TRIGGER dbo.TransactionChange ON dbo.Transactions
AFTER INSERT, UPDATE, DELETE
AS
BEGIN
IF @@ROWCOUNT = 0 OR
TRIGGER_NESTLEVEL
(
OBJECT_ID(N'dbo.TransactionChange', N'TR'),
'AFTER',
'DML'
) > 1
RETURN;
SET NOCOUNT, XACT_ABORT ON;
CREATE TABLE #Delta
(
AccountID integer PRIMARY KEY,
Amount money NOT NULL
);
INSERT #Delta
(AccountID, Amount)
SELECT
InsDel.AccountID,
Amount = SUM(InsDel.Amount)
FROM
(
SELECT AccountID, Amount
FROM Inserted
UNION ALL
SELECT AccountID, $0 - Amount
FROM Deleted
) AS InsDel
GROUP BY
InsDel.AccountID;
UPDATE A
SET Balance += D.Amount
FROM #Delta AS D
JOIN dbo.Accounts AS A WITH (SERIALIZABLE)
ON A.AccountID = D.AccountID
OPTION (RECOMPILE);
END;
Test yapmak
Aşağıdaki kod, sıfır bakiyeye sahip 100.000 hesap oluşturmak için bir sayı tablosu kullanır :
INSERT dbo.Accounts
(AccountID, Balance)
SELECT
N.n, $0
FROM dbo.Numbers AS N
WHERE
N.n BETWEEN 1 AND 100000;
Aşağıdaki test kodu 10.000 rastgele işlem ekler:
INSERT dbo.Transactions
(AccountID, Amount)
SELECT
CONVERT(integer, RAND(CHECKSUM(NEWID())) * 100000 + 1),
CONVERT(money, RAND(CHECKSUM(NEWID())) * 500 - 250)
FROM dbo.Numbers AS N
WHERE
N.n BETWEEN 1 AND 10000;
SQLQueryStress aracını kullanarak, bu testi iyi işlenmiş, kilitlenmemiş ve doğru sonuçları olan 32 iş parçacığında 100 kez çalıştırdım. Bunu hala bir öğrenme alıştırması dışında bir şey olarak önermiyorum.