アプリの監査証跡テーブルとして機能するテーブルがあります。それをProductAudit
と呼び、この監査テーブルのデータテーブルをProducts
と呼びます。 ProductsAudit
は、バグが原因で約1億5,000万行で汚染されました。残す必要がある有効な行は約200万行あります。
だからクリーンアップのために、私はしたい:
ProductAudit_TEMP
ProductAudit
ProductAudit_TEMP
からProductAudit
へ。これは、1億5000万の無効な行を削除するよりもはるかに高速です(バッチの場合でも)。
私の質問は、テーブルProducts
には、テーブルProductAudit
に書き込むINSERT、UPDATE、およびDELETEトリガーがあります。私の計画はトリガーを破りますか?私はすべてのユーザーをシステムから除外し、クリーンアップを実行するときに安全のためにトリガーを無効にします。
編集:
明確にするために、ProductAudit
にはトリガーがありませんが、Products
にはあります。 ProductAudit
は、テーブルProducts
に属するINSERT、UPDATE、およびDELETEトリガーによって入力されています。
@AaronBertrandがコメントで指摘したように、オブジェクトは他のオブジェクトにobject_idによってアタッチされ、オブジェクトname。そうは言っても、sp_rename
を使用せずに、あるテーブルから別のテーブルにデータを移動する方法があります。
例のコードは何かがどのように機能するかを示す最もクリーンな方法であるため、次のテストベッドコードを作成して、ALTER TABLE ... SWITCH
構文を使用してデータ移行を有効にする方法を示します。
最初に、Product
テーブルに行を挿入するProductAudit
テーブルのトリガーを使用して、「既存」のProduct
およびProductAudit
テーブルを作成します:
USE tempdb;
IF OBJECT_ID('dbo.Product') IS NOT NULL
DROP TABLE dbo.Product;
CREATE TABLE dbo.Product
(
ProductKey int NOT NULL
CONSTRAINT PK_Product
PRIMARY KEY CLUSTERED
IDENTITY(1,1)
, SomeData varchar(4000) NOT NULL
, TriggerUpdated bit NOT NULL
CONSTRAINT df_Product_TriggerUpdated
DEFAULT (0)
);
GO
IF OBJECT_ID('dbo.ProductAudit') IS NOT NULL
DROP TABLE dbo.ProductAudit;
CREATE TABLE dbo.ProductAudit
(
ProductAuditKey int NOT NULL
CONSTRAINT PK_ProductAudit
PRIMARY KEY CLUSTERED
IDENTITY(1,1)
, ProductKey int NOT NULL
, SomeData varchar(4000) NOT NULL
);
GO
CREATE TRIGGER dbo.ProductAuditInsertTrigger
ON dbo.Product
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO dbo.ProductAudit (ProductKey, SomeData)
SELECT i.ProductKey
, i.SomeData
FROM inserted i
UPDATE dbo.Product
SET TriggerUpdated = 1
FROM dbo.Product p
INNER JOIN inserted i ON p.ProductKey = i.ProductKey;
END
GO
ここでは、100,000行をProduct
テーブルに挿入します。
;WITH src AS (
SELECT *
FROM (VALUES(0), (1), (2), (3), (4), (5), (6), (7), (8), (9)) v(num)
)
INSERT INTO dbo.Product (SomeData)
SELECT CONVERT(varchar(4000), CRYPT_GEN_RANDOM(4000))
FROM src s1
CROSS APPLY src s2
CROSS APPLY src s3
CROSS APPLY src s4
CROSS APPLY src s5;
SELECT ProductCount = COUNT(*)
FROM dbo.Product pa
WHERE pa.TriggerUpdated = 1;
SELECT ProductAuditCount = COUNT(*)
FROM dbo.ProductAudit pa;
上記のSELECT
クエリの結果:
ProductCount ------------ 100000 ProductAuditCount ---------- ------- 100000
これにより、保持したい行を保持する「一時」テーブルが作成されます。
IF OBJECT_ID('dbo.ProductAuditNew') IS NOT NULL
DROP TABLE dbo.ProductAuditNew;
CREATE TABLE dbo.ProductAuditNew
(
ProductAuditKey int NOT NULL
CONSTRAINT PK_ProductAuditNew
PRIMARY KEY CLUSTERED
IDENTITY(1,1)
, ProductKey int NOT NULL
, SomeData varchar(4000) NOT NULL
);
このコードは既存のテーブルから新しいテーブルにデータを「移行」し、元のテーブルに存在するトリガーを再作成せずに保持します。 ALTER TABLE ... SWITCH
構文は非常に高速なメタデータのみの操作であり、データを移行するための優れた方法です。以下のコードは トランザクション分離レベル を設定して、関連するテーブルをロックし、他のプロセスが関連するテーブルにデータを挿入または更新しないようにします。
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; --lock all row-ranges affected by
--the migration
DECLARE @msg varchar(1000);
BEGIN TRANSACTION
BEGIN TRY
--disable the trigger temporarily
DISABLE TRIGGER dbo.ProductAuditInsertTrigger ON dbo.Product;
--copy the rows we want to retain from the old table to the new table
INSERT INTO dbo.ProductAuditNew (ProductKey, SomeData)
SELECT pa.ProductKey
, pa.SomeData
FROM dbo.ProductAudit pa
WHERE pa.ProductAuditKey % 5 = 0; --retain 20% of the rows
--truncate the "old" table
TRUNCATE TABLE dbo.ProductAudit;
--switch the single partition belonging to the "new" table
--into the "old" table
ALTER TABLE dbo.ProductAuditNew
SWITCH TO dbo.ProductAudit;
--remove the "new" table, which is actually empty now
DROP TABLE dbo.ProductAuditNew;
--re-seed the table's identity object; only necessary if an IDENTITY column is present
DBCC CHECKIDENT('dbo.ProductAudit', RESEED);
--re-enable the trigger
ENABLE TRIGGER dbo.ProductAuditInsertTrigger ON dbo.Product;
COMMIT TRANSACTION
END TRY
BEGIN CATCH
SET @msg = ERROR_MESSAGE();
PRINT (@msg);
ROLLBACK TRANSACTION
END CATCH
SET TRANSACTION ISOLATION LEVEL READ COMMITTED; --return to default mode
SELECT ProductAuditCount = COUNT(*)
FROM dbo.ProductAudit pa;
結果:
ProductAuditCount ----------------- 20000
ここでは、dbo.Product
テーブルに別の10行を挿入して、トリガーが引き続き機能することを確認します。
;WITH src AS (
SELECT *
FROM (VALUES(0), (1), (2), (3), (4), (5), (6), (7), (8), (9)) v(num)
)
INSERT INTO dbo.Product (SomeData)
SELECT CONVERT(varchar(4000), CRYPT_GEN_RANDOM(4000))
FROM src s1;
SELECT ProductAuditCount = COUNT(*)
FROM dbo.ProductAudit pa;
結果:
ProductAuditCount ----------------- 20010
トリガーがProductsテーブルにあり、ProductAuditテーブルに書き込んでいる場合は、トリガーの実行時にProductAuditという名前のテーブルが存在していれば問題ありません。
トリックは、ProductAuditからProductAudit_TEMPにすべての適切な行を既に移動したと考えた後、トリガーによって書き込まれる監査行がないことを確認することです。これが起こらないようにする簡単な方法は、データベースをRESTRICTED_USERモードなどに設定し、他のユーザーが接続できないようにすることです。アプリケーションを完全に停止せずに実行する必要がある場合は、TABLOCK、XLOCK、HOLDLOCKヒントを含むトランザクションですべてを実行するか、トリガーを一時的に変更してProductAudit_TEMPに直接書き込む必要があります。