【问题标题】:Avoid lock of tables when inserting into from stored procedure从存储过程插入时避免锁定表
【发布时间】:2018-04-28 23:37:00
【问题描述】:

我想通过使用 insert into 从存储过程返回一个输出。出于性能原因,目标表是内存优化表类型。 我现在发现,当存储过程运行时,存储过程中所有受影响的行都保持锁定,直到存储过程完成。

例子:

insert into @ModifiedSecurities (SecurityID, AttributeTypeID)
    exec Securities.spSecuritiesImportBody
                @ProcessingID = @ProcessingID

在执行Securities.spSecuritiesImportBody 期间(最多需要10 分钟),所有受spSecuritiesImportBody 影响的表行都被锁定,直到存储过程完成(甚至这些表都与存储过程的输出无关)。

虽然在单个插入语句中这种行为可能有意义,但我看不到它有任何用途,因此想摆脱这些锁。

有没有办法在不创建这些锁的情况下执行存储过程?

这是我制作的代码示例:

  1. 执行准备工作
  2. 运行代码
  3. 在代码运行时尝试从 dbo.ProcessingsTesting 中进行选择。这是不可能的,因为桌子被锁定了。在 dbo.UpdProcessing 期间正在创建锁。但是,由于某种原因,锁没有被释放。

选择 * 来自 dbo.ProcessingsTesting

--准备开始

drop procedure dbo.UpdProcessing 
drop table dbo.ProcessingsTesting
drop procedure dbo.spSecuritiesImportBody  


go


create table dbo.ProcessingsTesting
(
    ProcessingID int,
    EndDate datetime
)

insert into dbo.ProcessingsTesting
(
    ProcessingID
)
select 1 union all
select 2 union all
select 3 union all
select 4 union all
select 5


-- stored procedure
go
create procedure dbo.spSecuritiesImportBody  
(
    @ProcessingID int
)
as
begin


    exec dbo.UpdProcessing  
        @ProcessingID = @ProcessingID


    WAITFOR DELAY '00:03:00' 


    -- return data

    select 1, 2


end


-- stored procedure

go
create procedure dbo.UpdProcessing  
(
    @ProcessingID int
)
as
begin


    update dbo.ProcessingsTesting
    set EndDate = null
    where ProcessingID = @ProcessingID


end

--准备结束

-- run the code 

declare @ModifiedSecurities table
(
    [SecurityID] [int] NOT NULL,
    [AttributeTypeID] [smallint] NOT NULL
)


insert into @ModifiedSecurities (SecurityID, AttributeTypeID)
    exec  dbo.spSecuritiesImportBody 
        @ProcessingID = 1

【问题讨论】:

  • 显示存储过程的代码,可能有一个事务覆盖了整个事情,需要调整得更细。
  • 如果源是基于磁盘的,您可以分离阶段。有一个阶段从 SNAPSHOT 隔离级别的基于磁盘的表中读取,将结果推送到中间表。然后,将中间结果推送到内存优化表。我希望您可以执行跨容器 SNAPSHOT 事务,但您不能。在这种情况下,我已经多次成功使用此方法。
  • 不买锁不释放。这个例子肯定不会锁定其他表。

标签: sql sql-server tsql stored-procedures sql-server-2016


【解决方案1】:

除非您开始并提交显式事务,否则锁定将保留在已修改的行上,直到最外面的 INSERT...EXEC 语句完成。您可以向dbo.UpdProcessing proc 添加显式事务(或用BEGIN TRANCOMMIT 包围EXEC dbo.UpdProcessing)以在INSERT...EXEC 完成之前释放更新行上的锁:

ALTER PROCEDURE dbo.UpdProcessing  
(
    @ProcessingID int
)
AS

BEGIN TRAN;

UPDATE dbo.ProcessingsTesting
SET EndDate = null
WHERE ProcessingID = @ProcessingID

COMMIT;
GO

虽然这将提供所需的结果,但对我来说,在相同的存储过程中更新与SELECT 结果无关的数据并没有多大意义。似乎应该独立调用 proc,因为它们执行不同的功能。

【讨论】:

    【解决方案2】:

    在过程 spSecuritiesImportBody 上,您应该更改执行更新 exec dbo.UpdProcessing
    @ProcessingID = @ProcessingID 到 执行 dbo.UpdProcessing @ProcessingID 因为这样使用没有意义。

    您无法避免锁定,因为 MSSQL 服务器对行、表、页面使用锁定,以确保在服务器执行有关数据库上该记录的语句时您不会更改数据。

    【讨论】:

    • 嗨!欢迎来到堆栈溢出。原始代码原样没有问题,因为它的目的是将 EndDate 设置为 NULL。这可能表明该进程正在运行。此外,有很多方法可以避免锁定。查看各种事务隔离级别以了解更多详细信息。
    • 无法使用比较运算符(例如 =、)测试或更新 NULL 值。为了测试 NULL 值,我们将使用 IS NULL 或 IS NOT NULL 运算符,因为这里处理的是三个值字段(true、false 和 NULL),并且需要以这种形式处理 NULL。
    • 我同意你的观点,不同的隔离级别,但默认情况下是读取提交。
    • 不正确;分配 Column = NULL(或计算结果为 NULL 的表达式)是在 UPDATE 语句中使列 NULL 的唯一方法。 OP 没有在谓词中使用“= NULL”,它是赋值的一部分。
    猜你喜欢
    • 1970-01-01
    • 2011-12-03
    • 2019-12-06
    • 1970-01-01
    • 2021-03-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-24
    相关资源
    最近更新 更多