试图锁定数据库但不工作

时间:2013-05-23 21:08:31

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

我有一张看起来像这样的表

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中将值返回给我的模型。我不确定这是否是正确的方法,但是当我尝试从网络服务器调用存储过程时它会起作用

此存储过程经常被调用,并且可能同时具有两个相同的内容。唯一键可以防止存储坏数据,但我不想提交并捕获一个唯一的键异常,以便在它们发生时处理并发插入。我可以在不破坏性能的情况下锁定表吗?

我假设这会锁定Items以便2个请求无法同时插入它(并导致重复的id),并且他们无法尝试将相同的值插入表中两次(带有UniqueCol = uniqueval的2行

我是不是真的这样做了?我只是尝试并行调用这个存储过程几次,其中一个给了我一个错误,说有一个重复的PK违规。

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

2 个答案:

答案 0 :(得分:1)

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

  
      
  • 语句无法读取已修改但尚未修改的数据   由其他交易承诺。

  •   
  • 没有其他事务可以修改已被读取的数据   直到当前交易完成的当前交易。

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

  •   

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

我相信,当您的if not exists条款失败时,您的交易不会读取任何数据。 (它失败了,因为你想要的id不存在。)所以这个SELECT没有获得锁。

你可以使用TABLOCK table hint锁定一张桌子,但我不认为我曾见过有人在现实生活中这样做过。它对并发访问不利。

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

答案 1 :(得分: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

希望这有帮助。