【问题标题】:Trying to lock database but not working试图锁定数据库但不工作
【发布时间】:2013-05-25 07:59:15
【问题描述】:

我有一张像这样的表

Create Table Items(
   [Id] [int] IDENTITY(1,1) NOT NULL PRIMARY KEY,
   [UniqueCol] [nvarchar](20) NOT NULL,
   [Col] int NOT NULL
)

ALTER TABLE Items ADD CONSTRAINT UN_UniqueCol UNIQUE(UniqueCol)

我在 sql server 2008 中有一个如下所示的存储过程。

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRANSACTION

if not exists(select * from Items where UniqueCol=uniqueval)
begin
    insert into Items (UniqueCol, Col) values (uniqueval, val)
end
select * from Items where UniqueCol = uniqueval
COMMIT TRANSACTION

编辑:此合并工作正常

MERGE INTO Items as Target USING (VALUES(uniqueval)) as source (UniqueCol)
on (source.UniqueCol = Target.UniqueCol)
WHEN MATCHED THEN
   update set col = val
WHEN NOT MATCHED BY Target THEN
    insert (UniqueCol, col) VALUES (uniqueval, val);

我在 asp.net mvc 中运行一个网站,该网站经常遇到此过程。我在存储过程中执行此操作的原因是,我认为这是处理当两件事情试图同时插入相同的事情时发生的并发问题的最简单的地方。

最后的选择以某种方式将值返回给我在 asp.net mvc 中的模型。我不确定这是否是正确的方法,但是当我尝试从网络服务器调用存储过程时它可以工作

这个存储过程被非常频繁地调用,并且可能同时使用 2 个相同的东西。唯一键可防止存储错误数据,但我希望不必提交并捕获唯一键异常以在并发插入发生时处理它们。我可以在这里锁定表而不破坏性能吗?

我假设这会锁定项目,因此 2 个请求无法同时尝试插入其中,(并导致重复的 id)并且他们无法尝试将相同的值插入表中两次( 2 行,UniqueCol=uniqueval)

我这样做不正确吗?我只是尝试并行调用这个存储过程几次,一次给我一个错误,说存在重复的 PK 违规。

似乎设置事务隔离级别并不意味着以这种方式锁定表。有人可以解释它的含义以及为此类事务锁定表的正确方法吗?

【问题讨论】:

  • 我希望“Id”被声明为主键。
  • 哦,对不起,是的。我忘了把它放进去。
  • INSERT 命令only 锁定它新插入的行 - 没有别的。它不会锁定整张桌子 - 这是一件好事!否则,性能会受到严重影响......

标签: sql asp.net-mvc sql-server-2008 stored-procedures


【解决方案1】:

最好的解决方案是让 MERGE 工作。

但这是使用原始解决方案解决问题的方法:

问题是您使用锁定语义的方式与多线程编程中使用的方式相同。这有点不同。保证两个序列化事务在事务期间的读取保持不变,就像每个事务开始时看到的那样。

因此,具有 T1 的 user1 启动并且他认为该值未创建,因此他开始创建一个新行。带有 T2 的 User2,在 T1 尚未提交时开始(即在中间),它将开始读取该值。 因为 T1 尚未提交,该值是隔离的,不会被 T2/user2 看到,因此也会尝试插入新行。

两个事务都将尝试使用该值创建两行。

要使您的逻辑按预期工作,您需要将事务隔离级别设置为:READ UNCOMMITTED。
这将允许 T2 看到 T1 所做的更改,甚至在 T1 提交之前。
在插入之后添加另一个读取。如果它与当前值相同,则继续提交,否则回滚。这实际上是“乐观并发”,但由您在 SQL 级别实现。您也可以在应用程序级别执行此操作。

USE mytstdb;
-- This also applies the spirit of the double-checked locking pattern/concept
-- It also applies optimistic concurrency
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
BEGIN TRANSACTION

DECLARE @uniqueval NVARCHAR(20) = 'unique value';
DECLARE @val INT = 10;

DECLARE @valCount INT;

-- This is a dirty read, but for what you need it is OK
SET @valCount = (select count(*) from Items where UniqueCol=@uniqueval);
if(@valCount = 0)
begin
    insert into Items (UniqueCol, Col) values (@uniqueval, @val)
    SET @valCount = (select count(*) from Items where UniqueCol=@uniqueval);
    -- Optimistic concurrency
    if(@valCount > 1)
    begin
        ROLLBACK TRANSACTION;
        return;
    end
end

COMMIT TRANSACTION

希望这会有所帮助。

【讨论】:

  • 我尝试使用合并,但我看到相同的重复 PK 违规。知道为什么会发生这种情况吗?我将把我的合并命令放在问题中
  • 我得到了合并工作。我的消息来源毫无意义。我编辑了问题以显示来源应该是什么
【解决方案2】:

在 SQL Server 中,serializable 隔离被记录为具有以下行为。

  • 语句无法读取已修改但尚未修改的数据 由其他事务提交。

  • 没有其他事务可以修改已由 当前事务直到当前事务完成。

  • 其他事务无法插入具有以下键值的新行 将落在当前任何语句读取的键范围内 交易直到当前交易完成。

可序列化隔离不会锁定表。

我相信,当您的 if not exists 子句失败时,您的事务不会读取任何数据。 (它失败是因为您可能正在寻找的 id 不存在。)所以这个 SELECT 没有获取锁。

您可以使用TABLOCK table hint 锁定表,但我认为我在现实生活中从未见过有人这样做。这对并发访问不利。

您可能可以通过编辑您的问题并包含有关所有候选键(不仅仅是主键)以及您认为需要锁定表的原因的信息来获得更好的答案。与 SQL 问题一样,您最好的选择是为您的表包含 SQL DDL。 DDL 是最准确的描述;价值超过一千字。

【讨论】:

  • 是的,我读到了,这听起来像是我想要的。我猜不是。您能建议如何正确锁定它吗?
  • 我改名为 blah 和其他更具描述性的内容。
  • 另外,行为说“其他事务无法插入新行......”这不是锁定吗?
  • 再读一遍;它没有这么说。它说:“其他事务不能插入键值落在当前事务中的任何语句读取的键范围内的新行。”如果您要插入一个新密钥,那么您不可能读过它。
  • 啊,明白了。那么有没有办法安全地锁定我正在尝试做的事情?
猜你喜欢
  • 1970-01-01
  • 2023-03-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多