web-dev-qa-db-ja.com

トリガーのターゲットテーブルの名前変更

アプリの監査証跡テーブルとして機能するテーブルがあります。それをProductAuditと呼び、この監査テーブルのデータテーブルをProductsと呼びます。 ProductsAuditは、バグが原因で約1億5,000万行で汚染されました。残す必要がある有効な行は約200万行あります。

だからクリーンアップのために、私はしたい:

  • 新しいテーブルを作成します、ProductAudit_TEMP
  • 200万行を超えるコピー
  • ドロップテーブルProductAudit
  • 名前の変更ProductAudit_TEMPからProductAuditへ。

これは、1億5000万の無効な行を削除するよりもはるかに高速です(バッチの場合でも)。

私の質問は、テーブルProductsには、テーブルProductAuditに書き込むINSERT、UPDATE、およびDELETEトリガーがあります。私の計画はトリガーを破りますか?私はすべてのユーザーをシステムから除外し、クリーンアップを実行するときに安全のためにトリガーを無効にします。

編集:

明確にするために、ProductAuditにはトリガーがありませんが、Productsにはあります。 ProductAuditは、テーブルProductsに属するINSERT、UPDATE、およびDELETEトリガーによって入力されています。

2
HardCode

@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
4
Max Vernon

トリガーがProductsテーブルにあり、ProductAuditテーブルに書き込んでいる場合は、トリガーの実行時にProductAuditという名前のテーブルが存在していれば問題ありません。

トリックは、ProductAuditからProductAudit_TEMPにすべての適切な行を既に移動したと考えた後、トリガーによって書き込まれる監査行がないことを確認することです。これが起こらないようにする簡単な方法は、データベースをRESTRICTED_USERモードなどに設定し、他のユーザーが接続できないようにすることです。アプリケーションを完全に停止せずに実行する必要がある場合は、TABLOCK、XLOCK、HOLDLOCKヒントを含むトランザクションですべてを実行するか、トリガーを一時的に変更してProductAudit_TEMPに直接書き込む必要があります。

1
db2