0

10 ~ 15 個のテーブルへの変更を 1 回のトランザクションとしてコミットするアプリケーションからの複雑な作業単位があります。作業単位は、スナップショット分離で実行されます。

一部のテーブルには、ストアド プロシージャを実行してメッセージをキューに記録するトリガーがあります。メッセージには、テーブル名、キー、および変更タイプが含まれています。これは、SQL2005 との下位互換性を確保するために必要です。組み込みのキューイングを使用できません。

問題は、ストアド プロシージャを書き込むキューでブロックとタイムアウトが発生することです。次のようなメッセージが表示されます。

Snapshot isolation transaction aborted due to update conflict. You cannot use snapshot isolation to access table 'dbo.tblObjectChanges' directly or indirectly in database 

または、そのテーブルへの書き込みでタイムアウトが発生します。

トリガー内から、メッセージ キューの書き込みを行うストアド プロシージャへの (またはその中の) 特定の呼び出しのトランザクション分離を変更する方法はありますか? 最後の手段として、非同期で実行されるストアド プロシージャの削除部分または更新部分の呼び出しを行うことはできますか?

ストアド プロシージャの SQL は次のとおりです。

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[usp_NotifyObjectChanges]
    @ObjectType varchar(20),
    @ObjectKey int,
    @Level int, 
    @InstanceGUID varchar(50),
    @ChangeType int = 2

AS

SET NOCOUNT ON

DECLARE @ObjectChangeID int

--Clean up any messages older than 10 minutes
DELETE from tblObjectChanges Where CreatedTime < DATEADD(MINUTE, -10, GetDate())

--If the object is already in the queue, change the time and instanceID
SELECT @ObjectChangeID =  [ObjectChangeID]  FROM tblObjectChanges WHERE [ObjectType] = @ObjectType AND [ObjectKey] = @ObjectKey AND [Level] = @Level

IF NOT @ObjectChangeID is NULL
BEGIN
    UPDATE [dbo].[tblObjectChanges] SET
        [CreatedTime] = GETDATE(), InstanceGUID = @InstanceGUID 
    WHERE
        [ObjectChangeID] = @ObjectChangeID
END
ELSE
BEGIN
    INSERT INTO [dbo].[tblObjectChanges] (
        [CreatedTime],
        [ObjectType],
        [ObjectKey],
        [Level],
        ChangeType,
        InstanceGUID 
    ) VALUES (
        GETDATE(),
        @ObjectType,
        @ObjectKey,
        @Level,
        @ChangeType,
        @InstanceGUID 
    )
END

tblObjectChanges の定義:

CREATE TABLE [dbo].[tblObjectChanges](
    [CreatedTime] [datetime] NOT NULL,
    [ObjectType] [varchar](20) NOT NULL,
    [ObjectKey] [int] NOT NULL,
    [Rowversion] [timestamp] NOT NULL,
    [Level] [int] NOT NULL,
    [ObjectChangeID] [int] IDENTITY(1,1) NOT NULL,
    [InstanceGUID] [varchar](50) NULL,
    [ChangeType] [int] NOT NULL,
 CONSTRAINT [PK_tblObjectChanges] PRIMARY KEY CLUSTERED 
(
    [ObjectChangeID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 80) ON [PRIMARY]
) ON [PRIMARY]

GO
4

2 に答える 2

2

この行はほぼ間違いなくあなたの問題です:

DELETE from tblObjectChanges Where CreatedTime < DATEADD(MINUTE, -10, GetDate())

このステートメントには 2 つの大きな問題があります。まず、テーブル定義によると、CreatedTime はインデックス化されていません。これは、このステートメントを実行するには、テーブル全体をスキャンする必要があることを意味します。これにより、これがたまたまトランザクションの一部であるトランザクションの間、テーブル全体がロックされます。したがって、この列にインデックスを付けます。

2 つ目の問題は、インデックスがあったとしても、このような操作上のメンテナンス タスクをトリガー内から実行するべきではないということです。このステートメントを実行する必要がある OLTP トランザクションの速度が低下するだけでなく、このステートメントを実際に実行する必要があるのは 5 ~ 10 分ごとに 1 回だけです。代わりに、これらのテーブルのいずれかが変更されるたびに (そして毎回) 実行します。これは、システムがビジーになるにつれて悪化する追加の負荷です。

より良いアプローチは、このステートメントをトリガーから完全に取り除き、代わりに、このクリーンアップ操作を実行するために 5 ~ 10 分ごとに実行される SQL エージェント ジョブを用意することです。これをインデックスの追加とともに行うと、ほとんどの問題が解消されます。


追加の問題は、次のステートメントです。

SELECT @ObjectChangeID =  [ObjectChangeID]  FROM tblObjectChanges WHERE [ObjectType] = @ObjectType AND [ObjectKey] = @ObjectKey AND [Level] = @Level

上記の最初のステートメントとは異なり、このステートメントはトリガーに属します。ただし、最初のステートメントと同様に、投稿されたテーブル定義によると、検索対象の列にインデックスが付けられていないため、負荷がかかるとパフォーマンスとロックの問題が深刻になります (そして原因になります)。

解決策は、これらの列にも追加のインデックスを配置することです。

于 2013-01-16T23:29:23.417 に答える
1

いくつかのアイデア:

  • 可能であれば、削除を別のスケジュールされたジョブに移動します
  • CreatedTime にインデックスを追加する
  • ObjectType、ObjectKey、Level にインデックスを追加する
  • WITH(UPDLOCK, ROWLOCK) を SELECT に追加します
  • INSERT と UPDATE に WITH(ROWLOCK) を追加します。

これらすべてをテストして、何が役立つかを確認する必要があります。この順序で説明しますが、以下の注を参照してください。

これらすべてに反対することにした場合でも、少なくとも SELECT に WITH(UPDLOCK) を残しておいてください。そうしないと、更新が失われる可能性があります。

于 2013-01-16T02:36:20.977 に答える