检测存储过程中的脏读

l--*_*''' 20 sql t-sql sql-server

我有100个线程,每个线程调用下面定义的存储过程.

如何防止脏读?

SET QUOTED_IDENTIFIER OFF
SET ANSI_NULLS OFF
GO

ALTER procedure GetNextCerealIdentity
    (@NextKey int output, @TableID int)
AS
    declare @RowCount int, @Err int

    set nocount on

    select  
        @NextKey = 0

    begin transaction

Again:
    /*Update CfgCerealNumber Table */
    UPDATE CfgCerealNumber 
    SET CerealNumber = CerealNumber + 1  
    WHERE CerealNumberID = @TableID

    SELECT 
        @RowCount = @@RowCount, 
        @Err = @@Error      /*Obtain updated Cereal number previously incremented*/

    IF @Err <> 0            /* If Error gets here then exit         */
    BEGIN
        RAISERROR ('GetNextCerealIDSeries Failed with Error: %d TableID: %d ', 16, 1, @Err, @TableID)
        ROLLBACK TRANSACTION

        set nocount off
        return 1
    END

    IF @RowCount = 0                /* No Record then assume table is not   */
                                /* been initialized for TableID Supplied*/
    BEGIN
        RAISERROR('No Table Record Exists in CfgCerealNumber for ID:%d   ', 16, 1, @TableID)
        set nocount off
        Rollback Transaction
        return 1
    END

    /*Obtain updated Cereal number previously incremented*/
    SELECT @NextKey = CerealNumber 
    FROM CfgCerealNumber 
    WHERE CerealNumberID = @TableID

    SELECT @Err = @@Error                       /*Obtain updated Cereal number previously incremented*/

    IF @Err <> 0                            /* If Error gets here then exit         */
    BEGIN
        RAISERROR('GetNextCerealIDSeries Failed with Error: %d TableID: %d ', 16, 1, @Err, @TableID)
        Rollback Transaction    
        set nocount off
        return 1
    END

    commit transaction
    set nocount off
    return 0
GO
Run Code Online (Sandbox Code Playgroud)

看起来这个存储过程的部分在并行运行时返回0.01%的相同值:

SELECT @NextKey = CerealNumber 
FROM CfgCerealNumber 
WHERE CerealNumberID = @TableID
Run Code Online (Sandbox Code Playgroud)

我通过在事务中包装更新来以这种方式构造我的代码以防止脏读.

如何防止脏读?

Bac*_*its 5

如果您需要更新并返回更新的内容,那么我只想使用OUTPUT子句:

UPDATE CfgCerealNumber 
SET CerealNumber = CerealNumber + 1 
OUTPUT INSERTED.CerealNumber
WHERE CerealNumberID = @TableID;
Run Code Online (Sandbox Code Playgroud)

如果需要进行其他检查,则可以在从存储过程返回结果集之前将OUTPUT输出到声明的表变量中.

另一种方法是首先在表上创建一个阻塞锁,然后更新:

SELECT @CerealNumber = CerealNumber + 1 
FROM CfgCerealNumber WITH (HOLDLOCK, UPDLOCK) 
WHERE CerealNumberID = @TableID;

UPDATE CfgCerealNumber
SET CerealNumber = @CerealNumber
WHERE CerealNumberID = @TableID;
Run Code Online (Sandbox Code Playgroud)

但我会放钱,我看到这仍然会导致问题.我更相信它.


Dan*_*man 3

您可以使用联机丛书@variable = column = expression中描述的语法来避免该问题。此外,由于该语句在单语句自动事务中执行,因此您可以避免显式事务。

SET QUOTED_IDENTIFIER ON;
SET ANSI_NULLS ON;
GO

CREATE PROCEDURE GetNextSerialIdentity
      @NextKey int output
    , @TableID int
AS
SET NOCOUNT ON;

UPDATE dbo.CfgSerialNumber
SET @NextKey = SerialNumber = SerialNumber + 1
WHERE SerialNumberID = @TableID;

IF @@ROWCOUNT = 0
BEGIN
RAISERROR ('No Table Record Exists in CfgCerealNumber for ID:%d   ', 
                  16,1, @TableID);
END
GO
Run Code Online (Sandbox Code Playgroud)

  • AFAIK,您的原始过程可能返回重复的唯一原因是如果它是在允许脏读的“READ UNCOMMITTED”隔离级别中调用的。无论并发线程数和隔离级别如何,我发布的版本都不会为给定的 SerialNumberID 返回相同的值。 (3认同)