我有一个自动递增的主键OrderHeaderID,但我还需要根据订单的公司自动递增OrderNumber。
当前策略:(在我对表格的插入声明中)
(SELECT MAX(OrderNumber) + 1 FROM OrderHeader WHERE CompanyID = @CompanyID)
问题:一旦我开始测试一些卷,我开始收到重复的密钥错误:
表OrderHeader:
OrderHeaderID CompanyID OrderNumber
1 1 10000
2 1 10001
3 1 10002
4 2 10000
5 2 10001
6 2 10002
答案 0 :(得分:3)
要解决并发问题,您必须提高SELECT
语句的隔离级别,然后在同一事务中执行INSERT
。
每个RBDMS的具体语法会有所不同,但这是使用Sql Server的示例:
BEGIN TRANSACTION
DECLARE @OrderNumber INT
SELECT @OrderNumber = MAX(OrderNumber) + 1
FROM OrderHeader WITH (XLOCK)
WHERE CompanyID = @CompanyID
INSERT... (@OrderNumber)
COMMIT
这会对SELECT
语句中读取的行进行独占锁定,这将阻止此例程的另一个实例同时运行。相反,第二个实例将被阻止,直到原始进程执行INSERT
和COMMIT
,此时第二个实例将继续读取并锁定新生成的值。
答案 1 :(得分:1)
在这里查看我的答案:sql server: generate primary key based on counter and another column value
出于您的目的,您可以定义公司表格:
create table dbo.company
(
id int not null primary key ,
name varchar(32) not null unique ,
order_counter not null default(0) ,
...
)
和您的订单表:
create table dbo.order
(
company_id int not null foreign key references dbo.company( id ) ,
id int not null ,
order_number as 100000*company_id + id ,
...
constraint order_AK01 unique nonclustere ( order_number ) ,
constraint order_PK01 primary key clustered ( company_id , id ) ,
)
然后设置“添加订单”查询:
declare @new_order_number int
update dbo.company
set @new_order_number = dbo.company.order_counter + 1 ,
order_counter = dbo.company.order_counter + 1
where dbo.company.id = @some_company_id
insert dbo.order ( company_id , id ) value ( @some_company_id , @new_order_number )
您没有并发(种族)条件:“互锁更新”负责这一点。此外,您还没有对数据库设计进行非规范化(第一种正常形式要求每个行和列的交集都是原子 /不可分解的:它只包含来自适用域的一个值而不包含任何其他值。像你这样的识别者是 verboten 。)
容易!
答案 2 :(得分:0)
我会使用一个函数来自动增加每个给定ID的OrderNumber。
这是我在2分钟内放在一起的内容。它就是你所要求的。
create table dbo.OrderHeader (
OrderHeader int identity(1,1) primary key
,CompanyID int
,OrderNumber int
)
go
create function dbo.NextOrderNumber (
@CompanyID int
)
returns int
as
begin
declare @result int;
select @result = OrderNumber + 1
from OrderHeader
where CompanyID = @CompanyID
if @result is null
set @result = 10000
return @result;
end
go
insert into OrderHeader select 1, dbo.NextOrderNumber(1)
insert into OrderHeader select 2, dbo.NextOrderNumber(1)
insert into OrderHeader select 2, dbo.NextOrderNumber(1)
insert into OrderHeader select 1, dbo.NextOrderNumber(1)
select *
from OrderHeader