2

我有一个任意的存储过程usp_DoubleCheckLockInsertINSERT并且我想授予存储过程独占访问权限,以便SomeTable当它位于临界区Begin lockEnd lock.

CREATE PROCEDURE usp_DoubleCheckLockInsert
     @Id INT
    ,@SomeValue INT
AS
BEGIN
    IF (EXISTS(SELECT 1 FROM SomeTable WHERE Id = @Id AND SomeValue = @SomeValue)) RETURN
    BEGIN TRAN
        --Begin lock
        IF (EXISTS(SELECT 1 FROM SomeTable WHERE Id = @Id AND SomeValue = @SomeValue)) ROLLBACK

        INSERT INTO SomeTable(Id, SomeValue)
        VALUES(@Id,@SomeValue);
        --End lock
    COMMIT
END

我已经看到了隔离级别与更新的关系,但是有没有办法在关键部分实现锁定,给事务写锁,或者 TSQL 不是这样工作的?

在 SQL Server 中的存储过程开始时获取更新表锁

4

5 回答 5

2

对我有用的第二种方法是将 theINSERT和 the组合SELECT成一个操作。

此索引仅用于有效查询SomeTable。请注意,没有唯一性约束。但是,如果我采用这种方法,我实际上会使索引独一无二。

CREATE INDEX [IX_SomeTable_Id_SomeValue_IsDelete] ON [dbo].[SomeTable]
(
    [Id] ASC,
    [SomeValue] ASC,
    [IsDelete] ASC
)

存储过程,它结合了 INSERT/SELECT 操作:

CREATE PROCEDURE [dbo].[usp_DoubleCheckLockInsert]
     @Id INT
    ,@SomeValue INT
    ,@IsDelete bit
AS
BEGIN

        -- Don't allow dirty reads

        SET TRANSACTION ISOLATION LEVEL READ COMMITTED

    BEGIN TRAN
        -- insert only if data not existing
        INSERT INTO dbo.SomeTable(Id, SomeValue, IsDelete)
        SELECT @Id, @SomeValue, @IsDelete
        where not exists (
            select * from dbo.SomeTable WITH (HOLDLOCK, UPDLOCK)
            where Id = @Id
            and SomeValue = @SomeValue
            and IsDelete = @IsDelete)

    COMMIT
END

我确实尝试过使用多个进程插入数据的这种方法。(尽管我承认我并没有对 SQL Server 施加太大压力)。从来没有任何重复或失败的插入。

于 2013-05-06T00:48:49.430 回答
1

看来您要做的只是防止插入重复的行。您可以通过使用以下选项添加唯一索引来做到这一点IGNORE_DUP_KEY = ON

CREATE UNIQUE INDEX [IX_SomeTable_Id_SomeValue_IsDelete]
ON [dbo].[SomeTable]
(
    [Id] ASC,
    [SomeValue] ASC,
    [IsDelete] ASC
) WITH (IGNORE_DUP_KEY = ON)

SQL Server 将忽略任何具有重复键的插入。运行以下:

INSERT INTO [dbo].[SomeTable] ([Id],[SomeValue],[IsDelete])
VALUES(0,0,0)

INSERT INTO [dbo].[SomeTable] ([Id],[SomeValue],[IsDelete])
VALUES(1,1,0)

INSERT INTO [dbo].[SomeTable] ([Id],[SomeValue],[IsDelete])
VALUES(2,2,0)

INSERT INTO [dbo].[SomeTable] ([Id],[SomeValue],[IsDelete])
VALUES(0,0,0)

结果是:

(1 row(s) affected)

(1 row(s) affected)

(1 row(s) affected)

Duplicate key was ignored.
(0 row(s) affected)

我没有使用多个进程(线程)测试上述内容,但这种情况下的结果应该是相同的 - SQL Server 仍然应该忽略任何重复项,无论哪个线程正在尝试插入。

另请参阅MSDN 上的索引选项

于 2013-05-06T00:28:04.077 回答
0

I think I may not understand the question but why couldn't you do this:

begin tran
if ( not exists ( select 1 from SomeTable where Id = @ID and SomeValue = @SomeValue ) )
    insert into SomeTable ( Id, SomeValue ) values ( @ID, @SomeValue )
commit

Yes you have a transaction every time you do this but as long as your are fast that shouldn't be a problem.

I have a feeling I'm not understanding the question.

Jeff.

于 2013-05-02T15:07:19.710 回答
0

一旦你开始搞乱 sql 首选锁定管理,你就会承担责任,但如果你确定这是你需要的,请更新你的 sp 以选择一个测试变量并用那个变量替换你的“EXISTS”检查。当您查询变量时,请使用排他表锁,并且该表是您的,直到您完成。

CREATE PROCEDURE usp_DoubleCheckLockInsert
     @Id INT
    ,@SomeValue INT
AS
BEGIN
     IF (EXISTS(SELECT 1 FROM SomeTable WHERE Id = @Id AND SomeValue = @SomeValue)) RETURN
     BEGIN TRAN
        --Begin lock
         DECLARE @tId as INT
         -- You already checked and the record doesn't exist, so lock the table
         SELECT @tId 
         FROM SomeTable WITH (TABLOCKX)
         WHERE Id = @Id AND SomeValue = @SomeValue
         IF @tID IS NULL
           BEGIN
             -- no one snuck in between first and second checks, so commit
           INSERT INTO SomeTable(Id, SomeValue)
           VALUES(@Id,@SomeValue);
        --End lock
     COMMIT
END

如果您将其作为查询执行,但未命中提交,然后尝试从不同上下文的表中进行选择,您将坐下来等待提交生效。

于 2013-05-02T17:30:29.507 回答
0

Romoku,你得到的答案基本上是正确的,除了

  • 甚至不需要 BEGIN TRAN
  • 您无需担心隔离级别。

您所需要的只是insert ... select ... where not exists (select ...)Jeff B 和 Chue X 建议的简单方法。

您对并发的担忧(“我在谈论并发,而您的答案将不起作用。”)揭示了对 SQL 工作原理的深刻误解。

SQLINSERT 原子的。您不必锁定桌子;这就是 DBMS 为您所做的。

我建议不要为基于错误的先入为主的观念而提出的错误问题提供奖励——然后将正确的答案视为错误的答案——我建议坐下来阅读一本好书。在 SQL 上。如果你喜欢,我可以推荐一些标题。

于 2013-05-06T05:38:02.253 回答