为了实现特殊的 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。
这里存在许多锁定问题。
首先,正如@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
解决方案到底有什么问题。我能看到的唯一问题是它不稳定:如果您删除一行,那么后面的行将被重新编号,尽管无论如何这可能是需要的。