异步执行 SQL 或从触发器更改锁定

Posted

技术标签:

【中文标题】异步执行 SQL 或从触发器更改锁定【英文标题】:Execute SQL Asynchronously or change locking from Trigger 【发布时间】:2013-01-15 23:05:50 【问题描述】:

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

【问题讨论】:

不能在 SQL Server 2005 中使用 Service Broker?为什么不呢? 你超时让我怀疑。是否还有其他长时间运行的事务阻塞了重要数据? 有问题的超时总是在调用该存储过程时发生。 请提供tblObjectChanges的表定义,包括任何键、索引和任何其他触发器。 【参考方案1】:

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

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

与上面的第一条语句不同,这条语句属于触发器。但是,与第一条语句一样,它也会(并导致)在负载下出现严重的性能和锁定问题,因为再次,根据您发布的表定义,没有被搜索的列被索引。

同样的解决方案是在这些列上也放置一个附加索引。

【讨论】:

谢谢。我知道删除效率低下,但大多数实例都是针对 SQL Express 运行的,并且该表中通常只有 30-50 条记录,只有大约 10 个表使用这个“消息队列”。我将进行这些更改并找出一种更好的方法来不经常运行删除并看看它是如何进行的。感谢您的帮助。 我最终更改了代码以从带有时间戳的视图返回最后更新的值。再次感谢。【参考方案2】:

一些想法:

如果可能,将删除移动到单独的计划作业中 在 CreatedTime 上添加索引 在 ObjectType、ObjectKey、Level 上添加索引 将 WITH(UPDLOCK, ROWLOCK) 添加到 SELECT 中 将 WITH(ROWLOCK) 添加到 INSERT 和 UPDATE

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

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

【讨论】:

以上是关于异步执行 SQL 或从触发器更改锁定的主要内容,如果未能解决你的问题,请参考以下文章

SQL 数据更改时触发脚本

看看是啥导致了 SQL 触发器?

Sql触发器是同步还是异步?

Azure SQL 数据库中的异步触发器

Sql 触发器无法正常工作,删除到 logtable

SQL-Server:是不是有相当于一般存储过程执行的触发器