0

我有一个来自应用程序的复杂工作单元,它可能将更改作为单个事务提交到 10-15 个表。工作单元在快照隔离下执行。

一些表有一个触发器,它执行存储过程以将消息记录到队列中。该消息包含表名称、键和更改类型。这是提供与 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())

这种说法有两个问题。首先,根据您的表定义, CreatedTime 没有被索引。这意味着为了执行此语句,必须扫描整个表,这将导致整个表在它恰好属于的任何事务的持续时间内被锁定。所以在这个列上放一个索引。

第二个问题是,即使使用索引,您也不应该在触发器中执行这样的操作维护任务。除了减慢必须执行它的 OLTP 事务之外,该语句实际上只需要每 5-10 分钟执行一次。相反,您在任何时候(并且每次)修改这些表中的任何一个时都执行它。随着您的系统变得更加繁忙,这会增加很多额外的负载。

更好的方法是将这条语句完全从触发器中取出,而是让一个每 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
  • 将 WITH(ROWLOCK) 添加到 INSERT 和 UPDATE

您需要测试所有这些以查看有什么帮助。我将按此顺序浏览它们,但请参阅下面的注释。

即使您决定反对这一切,至少在 SELECT 上保留 WITH(UPDLOCK),否则您可能会丢失更新。

于 2013-01-16T02:36:20.977 回答