Jul*_*anR 5 sql sql-server locking race-condition
我有一个法律要求,在我们的应用程序(使用SQL Server)发票集合中,我们的编号不会有差距.因此,如果这些是发票号码,则不允许这样做:[1, 2, 3, 4, 8, 10]因为它不是顺序的.为此,我们InvoiceNumber在Invoices表格中有一栏.除此之外,我们还有一个InvoiceNumbers表格,其中包含每个组织的当前发票编号(因为每个组织都需要有自己的序列).存储过程是然后在填充负责InvoiceNumber上Invoices原子; 它或者在InvoiceNumbers表中将当前计数器递增1 并将该新值填入Invoices表中,或者在发生错误时回滚事务.这很好用.
现在添加了一项新要求:某些订单必须共享相同的发票,因此必须共享相同的发票编号,而以前每个订单都是单独开具发票的.为此,我们在当天开始时创建发票,并将其与当前FinancialPeriod(工作日,基本上)相关联,这将是每个订单使用的发票.但是,组织可能不会创建任何需要共享发票的类型的订单,因此在最初创建的发票(因为第二天创建新发票)的一天中没有任何内容可以开具发票并创建间隙.
现在,对我来说最简单的解决方案是懒洋洋地填写在InvoiceNumber当天开始时创建的共享发票.如果当天创建订单并且InvoiceNumber仍然是NULL,则创建该编号.这将确保InvoiceNumber永远不会被使用(Invoice记录未被使用并不重要,它没有任何实际意义).
为此,我创建了下面的存储过程,对于现有的过程,它会Invoice填充InvoiceNumber但仅在它仍然存在的情况下NULL.我只是不确定SQL Server如何锁定以及是否存在竞争条件的可能性,其中两个数据库事务决定InvoiceNumber仍然存在NULL并且将增加计数器并浪费一个数字,从而产生间隙.
从本质上讲,这个冗长的问题归结为:两个同步数据库事务是否可以决定if(@currentNumber is null)在@invoiceID此处进入相同的块?
你看到我从这里得到的锁定部分,但我不确定它是否适用于我的情况:
CREATE PROCEDURE [dbo].[CreateInvoiceNumber]
@invoiceID int,
@appID int
AS
BEGIN
SET NOCOUNT ON;
if not exists (select 1 from InvoiceNumbers where ApplicationID = @appID) insert into InvoiceNumbers values (@appID, 1)
declare @currentNumber int = null;
select @currentNumber = convert(int, i.InvoiceNumber)
from Invoices i
with (HOLDLOCK, ROWLOCK)
where i.ID = @invoiceID
if(@currentNumber is null)
begin
update InvoiceNumbers set @currentNumber = Value = Value + 1
where ApplicationID = @appID
update Invoices set InvoiceNumber = @currentNumber where ID = @invoiceID
end
select convert(nvarchar, @currentNumber)
END
Run Code Online (Sandbox Code Playgroud)
编辑
正如我的评论中所提到的,这些和其他写操作是从C#应用程序逻辑发起的数据库事务的一部分.只是一个普通的BeginTransaction在SqlConnection使用默认选项,这当然是在任何异常情况下回滚.
确保数据库隔离级别已设置为READ COMMITTED。
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
Run Code Online (Sandbox Code Playgroud)
这是默认的隔离级别。它确保在读取行之前必须提交所有事务,因此不会发生脏读。
还有一个重要的注意事项,在更新InvoiceNumbers表时,请确保它位于事务中,您希望在此处应用 ACID 原则,并且所有内容都是原子的(作为整个单元提交或事务回滚)。
| 归档时间: |
|
| 查看次数: |
1651 次 |
| 最近记录: |