SQL Server 中的读取、增量和更新事务

问题描述 投票:0回答:1

为了实现特殊的 ID,我们需要在当前日期后附加一个每天重置的身份计数器。例如,8 月 25 日的第一条记录的 ID 必须类似于 20240825-0001,并且每次插入记录时它必须递增。

由于当前表有一个标识列(自动递增的整数)以及一个

CreatedDate
类型的
datetime
列,我尝试使用以下查询来消除对辅助中间表的需要:

SELECT 
    ROW_NUMBER() OVER(PARTITION BY Cast(CreatedDate AS DATE) ORDER BY CreatedDate ASC) AS Row#,
    Id, 
    CreatedDate
FROM 
    MyBaseTable
WHERE
    CreatedDate > CAST('2024-08-25' AS DATE) 

但是,它无法提供所需的并发性。

我的第二次尝试是编写一个存储过程来保存插入记录时使用的第一个可用值。程序如下:

CREATE PROCEDURE [dbo].[GetSpecialId]
    (@CreatedDate date)
AS
BEGIN
    SET TRANSACTION ISOLATION LEVEL REPEATABLE READ

    BEGIN TRAN
        DECLARE @CurrentValue INT
        SET @CurrentValue = 1

        -- Insert initial row if necessary 
        IF NOT EXISTS (SELECT 1 FROM [dbo].[IndexingTable] 
                       WHERE CreationDate = @CreatedDate)
        BEGIN
            INSERT INTO [dbo].[IndexingTable] (CreationDate,  AvailableValue) 
            VALUES (@CreatedDate, 1)

            IF @@Error <> 0
            BEGIN
                ROLLBACK TRAN
                RETURN -1
            END 
        END
        ELSE
        BEGIN
            SELECT @CurrentValue = AvailableValue  
            FROM [dbo].[IndexingTable]

            UPDATE [dbo].[IndexingTable] 
            SET AvailableValue = AvailableValue + 1
            WHERE CreationDate = @CreatedDate 

            IF @@Error <> 0
            BEGIN
                ROLLBACK TRAN
                RETURN -1
            END
        END

        COMMIT TRAN

        SET TRANSACTION ISOLATION LEVEL REPEATABLE READ

        RETURN @CurrentValue
    END

然后,在

MyBaseTable
中创建新记录之前调用此存储过程。

我只是检查我是否错过了有关并发问题的任何内容或任何更好的解决方案。

我主要担心的是之前发生的一个事件,当时相同的代码输出良好并且每次调用都会递增,但

IndexingTable
中没有插入任何记录,这完全奇怪,因为我的 Visual Studio 调试器挂起并且无法恢复,甚至取消查询执行,SQL Server服务重启后一切恢复正常!

我正在使用具有最新累积更新的 SQL Server 2019。

sql-server stored-procedures concurrency transactions locking
1个回答
0
投票

这里存在许多锁定问题。

  • 首先,正如@AaronBertrand 所指出的,

    IF (NOT) EXISTS... INSERT ELSE ... UPDATE...
    序列不必要地复杂且性能不佳。您可以在
    @@ROWCOUNT
    之后使用
    UPDATE
    来检查它是否有效,然后有条件地插入否则。

  • 此外,

    REPEATABLE READ
    在这里还不够。服务器不知道
    SELECT @CurrentValue =
    用于保存先前的值,因此它只放置一个共享S锁,这在这里是不够的,你至少需要一个U锁。放置您需要的 U 型锁
    WITH (UPDLOCK)
    。使用
    SERIALIZABLE
    会更好地保持一致性。

  • 更好的选择是在执行

    UPDATE
    的同时设置变量,这会在更新发生之前给它值,除非您执行 @variable = column = newvalue
     在这种情况下,您会在之后获得值 
    . 似乎还存在一个问题,即当前代码在
    UDPATE
     的情况下给出先前的值,在 
    INSERT
     的情况下给出新值。您需要决定您想要哪个。

  • 最后,错误处理是不必要的。只需将

    SET XACT_ABORT ON;

     放在顶部,服务器就会自动处理回滚。

CREATE PROCEDURE dbo.GetSpecialId @CreatedDate date AS SET XACT_ABORT, NOCOUNT ON; BEGIN TRAN; DECLARE @CurrentValue INT = 0; UPDATE dbo.IndexingTable WITH (SERIALIZABLE) SET @CurrentValue = AvailableValue, -- gives the old value AvailableValue = AvailableValue + 1 -- for the new value use a single line: -- @CurrentValue = AvailableValue = AvailableValue + 1 WHERE CreationDate = @CreatedDate; IF @@ROWCOUNT = 0 BEGIN INSERT INTO dbo.IndexingTable (CreationDate, AvailableValue) VALUES (@CreatedDate, 1); END; COMMIT;
话虽如此,我不确定您的 

ROW_NUMBER

 解决方案到底有什么问题。我能看到的唯一问题是它不稳定:如果您删除一行,那么后面的行将被重新编号,尽管无论如何这可能是需要的。

© www.soinside.com 2019 - 2024. All rights reserved.