【问题标题】:There is no row but (XLOCK,ROWLOCK) locked it?没有行但(XLOCK,ROWLOCK)锁定它?
【发布时间】:2011-08-09 05:36:13
【问题描述】:

考虑一下这个简单的表格:

建表语句是:

CREATE TABLE [dbo].[Test_Serializable](
[Id] [int] NOT NULL,
[Name] [nvarchar](50) NOT NULL
)

所以没有任何主键或索引。

考虑它是空的并且没有任何行。我想插入这一行(1,'nima'),但我想检查是否存在Id=1 的行。如果是,则调用RAISERROR,如果没有插入行。我写了这个脚本:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRY
BEGIN TRAN ins

    IF EXISTS(SELECT * FROM Test_Serializable ts WITH(xlock,ROWLOCK) WHERE ts.Id=1)
        RAISERROR(N'Row Exists',16,1);


    INSERT INTO Test_Serializable
    (
        Id,
        [Name]
    )
    VALUES
    (
        1,
        'nima'
    )
   COMMIT TRAN ins
     END TRY
     BEGIN CATCH
        DECLARE @a  NVARCHAR(1000);
        SET @a=ERROR_MESSAGE();
        ROLLBACK TRAN ins
        RAISERROR(@a,16,1);
     END CATCH

这个脚本运行良好,但有一点很有趣。

我从 2 个 SSMS 运行此脚本并逐步运行这 2 个脚本(在调试模式下)。有趣的是,我的表没有行,但当到达 IF EXIST 语句锁定表时,其中一个脚本。

我的问题是(XLOCK,ROWLOCK) 是否会因为没有行而锁定整个表?还是会锁定幻像行:) !!???

编辑 1)

这是我的场景:

我有一个表格,例如 6 个字段

这是唯一性规则:

1)City_Code + F1_Code 是唯一的

2)City_Code + F2_Code 是唯一的

3)City_Code + F3_Code + F4_Code 是唯一的

问题是用户可能想要填写city_codeF1_Code,当它想要将其插入其他字段时,我们必须具有Empty String0(对于数字字段)值。

如果用户想填写 City_Code + F3_Code + F4_Code 那么 F1_Code 和 F2_Code 必须有 Empty String

我怎样才能更好地检查这个?我不能为每个规则创建任何唯一索引

【问题讨论】:

  • 这适用于 SQL Server 2005 还是 SQL Server 2008:这对解决方案很重要...

标签: sql sql-server sql-server-2005 sql-server-2008


【解决方案1】:

为了回答您的问题,SERIALIZABLE 隔离级别执行范围锁定,其中包括范围内不存在的行。

http://msdn.microsoft.com/en-us/library/ms191272.aspx

键范围锁定确保以下操作是 可序列化:

范围扫描查询

单例获取不存在的行

删除操作

插入操作

【讨论】:

  • 很好,你能多解释一下Singleton fetch of nonexistent row。这对我来说听起来很疯狂。谢谢
  • 可序列化扫描获取一个键范围锁,以防止在该范围内的任何位置插入任何新行(以及更新或删除该范围内的任何现有行)。这包括选择一个不存在的行(还没有?)单例提取只是选择一条记录,如果该行不存在,它仍然在事务期间锁定该键值。跨度>
【解决方案2】:

XLOCK 是排他锁:所以当 WHERE 遍历行时,行被锁定。

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE 与行的重复或锁定无关,它只是消除了"Phantom reads" 的可能性。从锁定的角度来看,它需要范围锁定(例如 A 和 B 之间的所有行)

因此,您可以使用 XLOCK 和 SERIALIZABLE 锁定表。您需要非排他性的 UPDLOCK。

使用 UPDLOCK,这种模式是不安全的。在高负载下,你仍然会得到重复错误,因为 2 个并发 EXISTS 不会找到一行,都尝试插入,一个得到重复错误。

所以只需尝试插入并捕获错误:

BEGIN TRAN ins
    INSERT INTO Test_Serializable
    (
        Id,
        [Name]
    )
    VALUES
    (
        1,
        'nima'
    )
   COMMIT TRAN ins
END TRY
BEGIN CATCH
   DECLARE @a  NVARCHAR(1000);
   IF ERROR_NUMBER() = 2627
     RAISERROR(N'Row Exists',16,1);
   ELSE
   BEGIN
     SET @a=ERROR_MESSAGE();
     RAISERROR(@a,16,1);
   END
   ROLLBACK TRAN ins
END CATCH

我已经提到过这个before

编辑:强制 SQL Server 2008 的各种唯一性

使用过滤索引

CREATE UNIQUE NONCLUSTERED INDEX IX_UniqueF1 ON (City_Code, F1_Code)
   WHERE F2_Code = '' AND F3_Code = '' AND AND F4_Code = 0;

CREATE UNIQUE NONCLUSTERED INDEX IX_UniqueF1 ON (City_Code, F2_Code)
   WHERE F1_Code = '' AND F3_Code = '' AND AND F4_Code = 0;

CREATE UNIQUE NONCLUSTERED INDEX IX_UniqueF3F4 ON (City_Code, F3_Code, F4_Code)
   WHERE F1_Code = '' AND F2_Code = '';

您可以对早期版本的索引视图执行相同操作

【讨论】:

  • +1 这是一本很好的读物,但现在我想知道。在这种情况下,隔离级别不处理重复项吗?
  • 谢谢,但问题是我的列不是主键,并且没有索引。如果我运行 INSERT,任何行都可以插入。为什么你说我可能会得到 2 行?我说过每个获得 IF EXISTS 的事务更早锁定表
  • @Lieven:不,它没有。隔离级别与重复检查无关:SERIALIZABLE 只是删除“幻读”
  • @Nima:在这种情况下,你不会关心重复,因为你没有唯一的约束。那么为什么要先测试呢?你的代码有什么意义......?假设您有 100 万行,那么整个表将被锁定,因为没有索引并且您要求锁定表(使用 XLOCK 和 SERIALIZABLE )。
  • 太棒了。非常感谢。你是一个很棒的 sql server 管理员。
猜你喜欢
  • 1970-01-01
  • 2015-04-05
  • 2017-09-07
  • 2011-12-10
  • 1970-01-01
  • 1970-01-01
  • 2011-07-17
  • 2021-04-23
  • 2011-10-05
相关资源
最近更新 更多