【问题标题】:Auto Increment Order Number (multiple companies in one table)自动递增订单号(一张表中的多个公司)
【发布时间】:2012-09-13 20:33:28
【问题描述】:

我有一个自动递增的主键 OrderHeaderID,但我还需要根据下订单的公司自动递增 OrderNumber。

当前策略:(在我对表格的插入语句中

(SELECT MAX(OrderNumber) + 1 FROM  OrderHeader WHERE CompanyID = @CompanyID)

问题:一旦我开始使用一些音量进行测试,我开始收到重复的键错误:

表格顺序表头:

OrderHeaderID CompanyID OrderNumber
1             1         10000
2             1         10001
3             1         10002

4             2         10000
5             2         10001
6             2         10002

【问题讨论】:

  • 你在使用什么 rdbms? sql-server,mysql,oracle?为什么要让订单号依赖于 companyid?
  • 我从变量名中假设他正在使用 SQL Server ...重新标记。

标签: sql sql-server auto-increment


【解决方案1】:

要解决并发问题,您必须提高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 语句期间读取的行设置一个排他锁,这将阻止该例程的另一个实例同时运行。相反,第二个实例将被阻塞,直到原始进程执行INSERTCOMMIT,此时第二个实例将继续读取并锁定新生成的值。

【讨论】:

  • 谢谢,非常简单,无需任何架构更改!
【解决方案2】:

在这里看看我的回答: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。)

简单!

【讨论】:

    【解决方案3】:

    我会使用一个函数来自动增加每个给定 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
    

    【讨论】:

    • 这个解决方案的问题在于它有一个内置的竞态条件 (en.wikipedia.org/wiki/Race_condition#Computing)。在您的 UDF 计算订单号和将该值插入到表中之间,另一个进程可以(并且将,在某些时候)出现,计算 same 订单编号并在您的进程完成插入之前将其插入表中。而且您会试图弄清楚为什么会出现看似随机的重复键错误(或者更糟糕的是,两个不同的订单合并为一个)。
    • 哦,我非常清楚这一点,而且在这种情况下,这就是答案的一半。事务和适当的隔离级别将是剩下的答案,这显然是我的一个忽略。
    猜你喜欢
    • 2014-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-07-26
    • 1970-01-01
    • 2015-07-30
    • 1970-01-01
    相关资源
    最近更新 更多