Tôi có yêu cầu pháp lý trong bộ sưu tập hóa đơn của ứng dụng (sử dụng SQL Server) của chúng tôi không thể có khoảng trống trong việc đánh số chúng. Vì vậy, nếu đây là số hóa đơn, điều này sẽ không được phép: [1, 2, 3, 4, 8, 10]
bởi vì nó không phải tuần tự. Để kết thúc, chúng tôi có một cột InvoiceNumber
trên bảng Invoices
của chúng tôi. Thêm vào đó, chúng tôi có một bảng InvoiceNumbers
chứa số hóa đơn hiện tại cho mỗi tổ chức (vì mỗi tổ chức cần phải có trình tự riêng của mình). Một thủ tục được lưu trữ sau đó chịu trách nhiệm điền vào số InvoiceNumber
trên Invoices
nguyên tử; nó sẽ tăng bộ đếm hiện tại lên 1 trong bảng InvoiceNumbers
và điền vào giá trị mới đó vào bảng Invoices
hoặc cuộn lại giao dịch trong trường hợp có lỗi. Điều này hoạt động tốt.Tạo số hóa đơn tuần tự trong SQL Server mà không có điều kiện chủng tộc
Bây giờ, yêu cầu mới đã được thêm vào: các đơn đặt hàng nhất định phải chia sẻ cùng một hóa đơn và do đó cùng một số hóa đơn, trong khi mỗi đơn hàng trước đó được lập hóa đơn riêng. Để kết thúc này, chúng tôi tạo một hóa đơn vào đầu ngày và kết hợp nó với FinancialPeriod
hiện tại (ngày làm việc, về cơ bản) sẽ là hóa đơn được sử dụng cho mọi đơn đặt hàng. Tuy nhiên, có thể tổ chức không tạo bất kỳ đơn đặt hàng nào thuộc loại yêu cầu lập hóa đơn được chia sẻ và do đó không có gì để lập hóa đơn trong một ngày mà "lãng phí" hóa đơn ban đầu được tạo ra (vì ngày hôm sau, một hóa đơn mới được tạo) và tạo lỗ hổng.
Bây giờ, giải pháp đơn giản nhất đối với tôi là phải điền nhẹ vào số InvoiceNumber
trên hóa đơn được chia sẻ được tạo vào đầu ngày. Nếu một đơn đặt hàng được tạo vào ngày đó và InvoiceNumber
vẫn là NULL
, thì hãy tạo số. Điều này sẽ đảm bảo một InvoiceNumber không bao giờ đi không sử dụng (nó không quan trọng là một bản ghi Invoice
đi không sử dụng, nó không có ý nghĩa thực sự).
Để kết thúc, tôi đã tạo quy trình được lưu trữ bên dưới, cho số Invoice
hiện tại, điền vào số InvoiceNumber
nhưng chỉ khi vẫn còn NULL
. Tôi chỉ không chắc chắn về cách SQL Server khóa và nếu có tiềm năng cho một điều kiện chủng tộc nơi hai giao dịch cơ sở dữ liệu quyết định rằng InvoiceNumber
vẫn là NULL
và cả hai sẽ tăng bộ đếm và lãng phí một số, tạo khoảng trống.
Về cơ bản, câu hỏi dài có này này tóm tắt: có thể hai giao dịch cơ sở dữ liệu đồng thời quyết định nhập khối if(@currentNumber is null)
cho cùng một số @invoiceID
tại đây không?
Các khóa phần bạn thấy tôi đã nhận được từ đây, nhưng tôi không chắc chắn nó áp dụng cho trường hợp của tôi:
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
EDIT
Như đã đề cập trong bình luận của tôi, những hoạt động viết và viết này là một phần của một giao dịch cơ sở dữ liệu được khởi tạo từ logic ứng dụng C#. Chỉ cần BeginTransaction
thông thường trên SqlConnection
với các tùy chọn mặc định, tất nhiên là được quay lại trong trường hợp có bất kỳ ngoại lệ nào.
Xem câu hỏi này và đó là câu trả lời cho giải pháp đã được thử và thực sự cho sự cố của bạn. http://dba.stackexchange.com/questions/36603/handling-concurrent-access-to-a-key-table-without-deadlocks-in-sql-server –
@MaxVernon - Đúng nếu tôi sai, nhưng dường như nhắm vào việc làm những gì tôi đang làm nhưng tránh và phục hồi từ những người chết, đó không phải là ưu tiên đối với tôi. Dù bằng cách nào, thật khó để chưng cất những gì tôi cần từ câu trả lời đó, vì nó khá phức tạp hơn những gì tôi có cho đến nay. – JulianR
Đó là lý do tại sao tôi không đăng câu trả lời đó. –