1.トリガーはリレーショナルデータベースのACID原則に従っていますか?挿入がコミットされる可能性はありますが、トリガーは失敗しますか?
この質問は、あなたがリンクした関連質問で部分的に回答されています。トリガーコードは、起動の原因となったDMLステートメントと同じトランザクションコンテキストで実行され、言及したACID原則のAtomic部分が保持されます。トリガーステートメントとトリガーコードは、1つの単位として成功または失敗します。
ACID特性はまた、明示的な制約(違反していない状態でデータベースを残します(トリガー・コードを含む)全体の取引を保証する一貫性)と任意の回復可能なコミットの効果が(データベースクラッシュ生き残る耐久性を)。
周囲の(おそらく暗黙的または自動コミット)トランザクションがSERIALIZABLE
分離レベルで実行されていない限り、Isolatedプロパティは自動的に保証されません。他の同時データベースアクティビティは、トリガーコードの正しい動作を妨げる可能性があります。たとえば、アカウント残高は、それを読んだ後、更新する前に別のセッションによって変更される可能性があります。これは、古典的な競合状態です。
2.私のIFおよびUPDATEステートメントは奇妙に見えます。正しい[アカウント]行を更新するより良い方法はありますか?
あなたがリンクした他の質問がトリガーベースのソリューションを提供しない非常に良い理由があります。非正規化された構造の同期を維持するように設計されたトリガーコードは、正しく動作して適切にテストするのが非常に難しい場合があります。長年の経験を持つ非常に高度なSQL Serverの人々でさえ、これに苦労しています。
すべてのシナリオで正確性を維持すると同時に良好なパフォーマンスを維持し、デッドロックなどの問題を回避すると、さらに困難な側面が追加されます。あなたのトリガーコードはどこか頑健に近く、単一のトランザクションのみが変更された場合でも、すべてのアカウントの残高を更新します。トリガーベースのソリューションにはあらゆる種類のリスクと課題があり、このテクノロジー領域に比較的新しい人には、このタスクは非常に不適切です。
問題の一部を説明するために、以下にいくつかのサンプルコードを示します。これは厳密にテストされたソリューションではなく(トリガーは難しい!)、私はそれを学習演習以外の目的で使用することはお勧めしません。実際のシステムの場合、非トリガーソリューションには重要な利点があるため、他の質問に対する回答を注意深く検討し、トリガーの考えを完全に回避する必要があります。
サンプルテーブル
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)
);
予防 TRUNCATE TABLE
トリガーはによって起動されませんTRUNCATE TABLE
。次の空のテーブルは、Transactions
テーブルが切り捨てられるのを防ぐためにのみ存在します(外部キーによって参照されることで、テーブルの切り捨てが防止されます)。
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)
);
トリガーの定義
次のトリガーコードは、必要なアカウントエントリのみが維持されるようにし、SERIALIZABLE
そこでセマンティクスを使用します。望ましい副作用として、これにより、行バージョンの分離レベルが使用されている場合に発生する可能性がある誤った結果も回避されます。また、ソースステートメントの影響を受ける行がない場合、コードはトリガーコードの実行を回避します。一時テーブルとRECOMPILE
ヒントは、不正確なカーディナリティー推定によって引き起こされるトリガー実行プランの問題を回避するために使用されます。
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;
テスト中
次のコードは、数値のテーブルを使用して、残高がゼロの100,000アカウントを作成します。
INSERT dbo.Accounts
(AccountID, Balance)
SELECT
N.n, $0
FROM dbo.Numbers AS N
WHERE
N.n BETWEEN 1 AND 100000;
以下のテストコードは、10,000のランダムトランザクションを挿入します。
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ツールを使用して、このテストを32スレッドで100回実行しました。パフォーマンスは良好で、デッドロックはなく、正しい結果が得られました。私はまだこれを学習演習以外のものとしてお勧めしません。