我有一个包含序号的表格(想想发票号码或学生证)。
在某些时候,用户需要请求前一个号码(以便计算下一个号码)。一旦用户知道当前号码,他们就需要生成下一个号码并将其添加到表格中。
我担心的是,由于并发访问,两个用户将能够错误地生成两个相同的号码。
我听说过存储过程,我知道这可能是一个解决方案。这里有最好的做法,以避免并发问题吗?
修改:这是我到目前为止所拥有的:
USE [master]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[sp_GetNextOrderNumber]
AS
BEGIN
BEGIN TRAN
DECLARE @recentYear INT
DECLARE @recentMonth INT
DECLARE @recentSequenceNum INT
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- get the most recent numbers
SELECT @recentYear = Year, @recentMonth = Month, @recentSequenceNum = OrderSequenceNumber
FROM dbo.OrderNumbers
WITH (XLOCK)
WHERE Id = (SELECT MAX(Id) FROM dbo.OrderNumbers)
// increment the numbers
IF (YEAR(getDate()) > IsNull(@recentYear,0))
BEGIN
SET @recentYear = YEAR(getDate());
SET @recentMonth = MONTH(getDate());
SET @recentSequenceNum = 0;
END
ELSE
BEGIN
IF (MONTH(getDate()) > IsNull(@recentMonth,0))
BEGIN
SET @recentMonth = MONTH(getDate());
SET @recentSequenceNum = 0;
END
ELSE
SET @recentSequenceNum = @recentSequenceNum + 1;
END
-- insert the new numbers as a new record
INSERT INTO dbo.OrderNumbers(Year, Month, OrderSequenceNumber)
VALUES (@recentYear, @recentMonth, @recentSequenceNum)
COMMIT TRAN
END
这似乎有用,并且给了我想要的价值。 到目前为止,我还没有添加任何锁定来防止并发访问。
编辑2 :添加WITH(XLOCK)
以锁定表,直到事务完成。我不打算在这里表演。只要我没有添加重复的条目,并且没有发生死锁,这应该可行。
答案 0 :(得分:6)
你知道SQL Server会为你做这件事,对吧?如果需要序列号或计算列,如果需要根据另一个计算新值,可以使用标识列。
但是,如果这不能解决您的问题,或者您需要进行复杂的计算来生成无法在简单插入中完成的新数字,我建议编写一个锁定表的存储过程,获取最后一个值,生成新值,插入它然后解锁表。
阅读此link以了解事务隔离级别
确保尽可能小的“锁定”时间
答案 1 :(得分:3)
以下是Counter实施示例。基本思路是使用插入触发器来更新发票的数量,发票。第一步是创建一个表来保存最后分配的数字的值:
create table [Counter]
(
LastNumber int
)
并用单行初始化它:
insert into [Counter] values(0)
样本发票表:
create table invoices
(
InvoiceID int identity primary key,
Number varchar(8),
InvoiceDate datetime
)
存储过程LastNumber首先更新Counter行,然后检索该值。由于该值是一个int,它只是作为过程返回值返回;否则将需要输出列。过程作为下一个数字的参数号来获取;输出是最后一个数字。
create proc LastNumber (@NumberOfNextNumbers int = 1)
as
begin
declare @LastNumber int
update [Counter]
set LastNumber = LastNumber + @NumberOfNextNumbers -- Holds update lock
select @LastNumber = LastNumber
from [Counter]
return @LastNumber
end
Invoice表上的触发器获取同时插入的发票的数量,从存储过程中询问下n个数字并更新具有该数字的发票。
create trigger InvoiceNumberTrigger on Invoices
after insert
as
set NoCount ON
declare @InvoiceID int
declare @LastNumber int
declare @RowsAffected int
select @RowsAffected = count(*)
from Inserted
exec @LastNumber = dbo.LastNumber @RowsAffected
update Invoices
-- Year/month parts of number are missing
set Number = right ('000' + ltrim(str(@LastNumber - rowNumber)), 3)
from Invoices
inner join
( select InvoiceID,
row_number () over (order by InvoiceID desc) - 1 rowNumber
from Inserted
) insertedRows
on Invoices.InvoiceID = InsertedRows.InvoiceID
如果发生回滚,则不会留下任何空隙。可以使用不同序列的键轻松扩展计数器表;在这种情况下,date-until可能很好,因为你可能事先准备好这个表,让LastNumber担心选择当前年/月的计数器。
使用示例:
insert into invoices (invoiceDate) values(GETDATE())
当数字列的值自动生成时,应该重新读取它。我相信EF有这方面的规定。
答案 2 :(得分:0)
我们在SQL Server中处理此问题的方法是在单个事务中使用UPDLOCK表提示。
例如:
INSERT
INTO MyTable (
MyNumber ,
MyField1 )
SELECT IsNull(MAX(MyNumber), 0) + 1 ,
"Test"
FROM MyTable WITH (UPDLOCK)
它并不漂亮,但由于我们提供了数据库设计,并且由于遗留应用程序访问数据库而无法更改它,因此这是我们可以提出的最佳解决方案。