【问题标题】:Why does row level locking not appear to work correctly in SQL server?为什么行级锁定在 SQL Server 中似乎无法正常工作?
【发布时间】:2010-02-25 16:02:35
【问题描述】:

这是When I update/insert a single row should it lock the entire table?的延续

这是我的问题。

我有一个持有锁的表,这样系统中的其他记录就不必对公共资源进行锁定,但仍然可以将任务排队,以便一次执行一个。

当我访问此锁定表中的记录时,我希望能够锁定并更新它(仅一条记录),而没有任何其他进程能够执行相同操作。我可以通过 updlock 之类的锁定提示来做到这一点。

发生的情况是,即使我使用行锁来锁定记录,它也会阻止对另一个进程的请求,以更改同一个表中完全不相关的行,该行也将指定 updlock 提示以及 rowlock。

您可以通过制作表格重新创建它

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[Locks](
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [LockName] [varchar](50) NOT NULL,
    [Locked] [bit] NOT NULL,
 CONSTRAINT [PK_Locks] PRIMARY KEY CLUSTERED 
(
    [ID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON, FILLFACTOR = 100) ON [PRIMARY]
) ON [PRIMARY]
GO
SET ANSI_PADDING OFF
GO
ALTER TABLE [dbo].[Locks] ADD  CONSTRAINT [DF_Locks_LockName]  DEFAULT ('') FOR [LockName]
GO
ALTER TABLE [dbo].[Locks] ADD  CONSTRAINT [DF_Locks_Locked]  DEFAULT ((0)) FOR [Locked]
GO

为 LockName='A' 的锁添加两行,为 LockName='B'

添加一行

然后创建两个查询以同时针对它在事务中运行:

查询 1:

Commit
Begin transaction
select * From Locks with (updlock rowlock) where LockName='A'

查询 2:

select * From Locks with (updlock rowlock) where LockName='B'

请注意,我将交易保持打开状态,以便您可以看到此问题,因为如果没有此打开的交易,它将不可见。

当您运行 Query 1 时,锁定是该行的问题,任何后续对 LockName='A' 的查询都必须等待。这种行为是正确的。

这有点令人沮丧的是,当您运行 Query 2 时,您会被阻止,直到 Query 1 完成,即使这些记录是不相关的记录。如果您然后像我上面那样再次运行 Query 1,它将提交上一个事务,Query 2 将运行,然后 Query 1将再次锁定记录。

请就我如何能够仅正确锁定一行而不阻止其他项目的更新提供一些建议。

PS。在更新其中一行后,Holdlock 也无法产生正确的行为。

【问题讨论】:

    标签: sql-server tsql locking


    【解决方案1】:

    在SQL Server 中,锁定提示应用于扫描的对象,不匹配。

    通常,引擎在读取对象(页面等)时会在对象(页面等)上放置共享锁,并在扫描完成后解除它们(或在 SERIALIZABLE 事务中不解除)。

    但是,您指示引擎放置(并解除)彼此不兼容的更新锁。

    事务B 锁定,同时尝试将UPDLOCK 放在已经被事务A 锁定的行上。

    如果您创建索引并强制使用它(因此不会发生冲突读取),您的表将不会锁定:

    CREATE INDEX ix_locks_lockname ON locks (lockname)
    
    Begin transaction
    select * From Locks with (updlock rowlock INDEX (ix_locks_lockname)) where LockName='A'
    
    Begin transaction
    select * From Locks with (updlock rowlock INDEX (ix_locks_lockname)) where LockName='B'
    

    【讨论】:

    • 这是一个很好的答案,感谢您发布有用的代码。有没有办法让sql默认这种模式?
    • @Middletone:什么是“默认”?
    • 对不起,我真的应该问如何让 SQL 使用索引而不是表扫描,因为这听起来有点像导致它不使用表上的索引的原因。有没有办法让它始终锁定主键,而不管它如何在表中找到记录?这是我对默认值的看法。
    • @Middletone:恐怕你不能(可靠地)。您可以尝试一些邪恶的魔法,包括使用不同锁定选项的表上的自连接并强制访问路径到嵌套循环。
    【解决方案2】:

    对于查询 2,尝试使用 READPAST 提示 - 此(引用):

    指定数据库引擎不 读取被其他人锁定的行 交易。大多数下 情况,同样适用于 页。当指定 READPAST 时, 行级锁和页级锁 被跳过。也就是数据库 引擎跳过行或页面 而不是阻塞当前 交易直到锁定 发布

    这通常用于队列处理类型的环境 - 因此多个进程可以从队列表中提取下一个项目而不会被其他进程阻塞(当然,使用 UPDLOCK 来防止多个进程拾取同一行) .

    编辑 1:
    如果您在 LockName 字段上没有索引,则可能会导致这种情况。使用索引,查询 2 可以对确切的行进行索引查找。但如果没有它,它将进行扫描(检查每一行),这意味着它会被第一个事务阻止。因此,如果它没有被索引,请尝试对其进行索引。

    【讨论】:

    • 如果我使用 readpast 并且另一个进程已锁定 b 则不会返回任何内容,这同样是个问题。这也没有解决原来的行级锁定不能正常工作的问题。
    • 再次更改索引没有任何作用。我已经麻烦发布了所有代码,因此请在发布答案之前测试您的解决方案。
    • @Middletone - 很公平。很多时候,实际上很难立即复制某人遇到的特定问题 - 除非可以这样做,否则并不总是可以说“这是解决您的问题的方法”。我有可行的解决方案,这是我在试图帮助您时所依据的答案 - 加上我的时间和精力。
    • 感谢您试一试。我仍然很感激。
    【解决方案3】:

    我不确定您要完成什么,但通常那些处理类似问题的人希望使用 sp_getapplock。托尼·罗杰森报道:Assisting Concurrency by creating your own Locks (Mutexs in SQL)

    【讨论】:

      【解决方案4】:

      如果您想在 SQL Server 中排队,请使用 UPDLOCK, ROWLOCK, READPAST 提示。它有效。

      我会考虑改变你的方法,而不是试图改变 SQL Server 的行为......

      【讨论】:

        猜你喜欢
        • 2020-07-13
        • 2019-05-16
        • 2010-10-18
        • 1970-01-01
        • 2021-04-27
        • 2017-09-05
        • 2017-09-04
        • 2012-05-06
        • 2013-02-17
        相关资源
        最近更新 更多