0

我有一张看起来像这样的桌子

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=唯一)

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

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

4

2 回答 2

1

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

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

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

因此,具有 T1 的 user1 启动并且他看到该值未创建,因此他开始创建一个新行。User2 与 T2,在 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

希望这可以帮助。

于 2013-05-23T22:58:35.637 回答
1

在 SQL Server 中,可序列化隔离被记录为具有以下行为。

  • 语句不能读取已被其他事务修改但尚未提交的数据。

  • 在当前事务完成之前,任何其他事务都不能修改当前事务已读取的数据。

  • 在当前事务完成之前,其他事务不能插入键值落在当前事务中的任何语句读取的键范围内的新行。

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

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

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

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

于 2013-05-23T21:29:46.320 回答