异步执行SQL或从Trigger更改锁定

时间:2013-01-15 23:05:50

标签: sql sql-server

我有一个复杂的工作单元来自一个应用程序,可能会将更改为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

2 个答案:

答案 0 :(得分:2)

这条线几乎肯定是你的问题:

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

此声明存在两个 BIG 问题。首先,根据您的表定义,CreatedTime未编入索引。这意味着,为了执行此语句,必须扫描整个表,这将导致整个表在发生这种情况的任何事务的持续时间内被锁定。所以在这个专栏上放一个索引。

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

更好的方法是将此语句完全从触发器中取出,而是使用每5-10分钟运行一次的SQL代理作业来执行此清理操作。如果您在添加索引的同时执行此操作,则大部分问题都将消失。


另一个问题是这句话:

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

与上面的第一个语句不同,此语句属于触发器。但是,与第一个语句一样,它也会在加载时产生(并导致)严重的性能和锁定问题,因为根据您发布的表定义,搜索的所有列都不会被编入索引。

解决方案是再次在这些列上添加一个索引。

答案 1 :(得分:1)

一些想法:

  • 如果可能,将删除移至单独的预定作业
  • 在CreatedTime上添加索引
  • 在ObjectType,ObjectKey,Level
  • 上添加索引
  • 将WITH(UPDLOCK,ROWLOCK)添加到SELECT
  • 将WITH(ROWLOCK)添加到INSERT和UPDATE

您需要测试所有这些以查看有用的内容。我会按顺序查看它们,但请参阅下面的注释。

即使你决定反对所有这一切,至少在SELECT上留下WITH(UPDLOCK),否则你可能会失去更新。