如何重新启动按ID自动递增的列

本文关键字:重新启动 ID | 更新日期: 2023-09-27 18:26:36

我们正在开发一个小企业使用的多租户系统,需要为其客户生成发票。基本上,由于明显的原因,每个租户都希望各自独立生成自己的发票编号序列。因此,第一个租户可以拥有发票1、2、3,第二个租户可以有相同的发票,因为他们是独立的企业,彼此一无所知。

我们使用实体框架7作为我们的ORM,使用SQL 2014作为我们的数据库。我们需要一种方法来生成这些发票号码,而不会在繁重的并发负载下意外复制同一租户的发票号码。最初在EF中,我们只是取发票列的最大值,其中租户=当前租户ID,然后加1,但经过压力测试,它为该租户创建了大量重复的发票号。触发器对此更好吗?序列?我不知道下一步该怎么办。

这里有一个简化的表格布局来描述我们的情况。请注意最后一个表,每个租户需要如何重新启动发票编号。这就是我们正在努力实现的

+----------+-------------+
| TenantID | TenantName  |
+----------+-------------+
|        5 | ABC Company |
|        6 | XYZ Corp    |
+----------+-------------+
+----------+------------+----------------+
| TenantID | CustomerID |  CustomerName  |
+----------+------------+----------------+
|        5 |          2 | Alpa Customer  |
|        5 |          5 | Beta Customer  |
|        6 |          3 | Delta Customer |
|        6 |          4 | Omega Customer |
+----------+------------+----------------+
    +----------+------------+-----------+-------------------------------------------------------+
| TenantID | CustomerID | InvoiceID | InvoiceNumber(this one needs to restart per tenant) |
+----------+------------+-----------+-------------------------------------------------------+
|        5 |          2 |         1 |                                                     1 |
|        5 |          2 |         7 |                                                     2 |
|        5 |          5 |         2 |                                                     3 |
|        5 |          5 |         4 |                                                     4 |
|        5 |          5 |         5 |                                                     5 |
|        6 |          3 |         8 |                                                     1 |
|        6 |          4 |         3 |                                                     2 |
|        6 |          4 |         6 |                                                     3 |
+----------+------------+-----------+-------------------------------------------------------+

基于@ERIKE的回答我最终在TenantID和InvoiceNumber周围添加了一个唯一的约束,然后我取发票编号的最大值,并添加一个,然后尝试插入。这是围绕C#中的do-while循环进行的。每当出现唯一约束错误时,它会再次重试

bool retryInsert;
do
{
    try
    {
        retryInsert = false;
        var invNo = (db.tbl_Invoice
            .Where(t => t.TenantID == invoice.TenantID)
            .Max(t => t == null ? 0 : t.InvoiceNumber)
            ) + 1;
        invoice.InvoiceNumber = invNo;
        db.tbl_Invoice.Add(invoice);
        db.SaveChanges();
    }
    catch (DbUpdateException ex)
    {
        retryInsert = false;
        var sqlexception = ex.InnerException as SqlException;
        if (sqlexception != null)
        {
            if (sqlexception.Errors.OfType<SqlError>()
                .Any(se => se.Number == 2627))
            {
                retryInsert = true;
            }
            else throw ex;
        }
    }
} while (retryInsert);
return invoice;

如何重新启动按ID自动递增的列

为了支持每秒最高的交易量,我认为您的最佳答案是在发票表中对(租户ID、发票ID)设置一个唯一索引,并进行并发插入,然后在违反唯一密钥的情况下重试。

我基于几年前读到的一篇关于在这种情况下实现最高吞吐量的文章。

无论如何,我强烈建议不要每个租户都有一张桌子。你可能会研究序列,但我不确定每个租户有一个序列是否合理。

每个租户有一行的"最后一张发票"表可以工作:

UPDATE dbo.TenantLastInvoice
SET
   LastInvoiceID = LastInvoiceID + 1,
   @InvoiceID = InvoiceID
WHERE TenantID = 123;

我不能100%确定您是否需要锁定提示,您可以考虑ROWLOCKUPDATELOCK,如果其他所有操作都失败,则考虑READPAST(如果没有更新,则重试)。

不要期望能够完全避免偶尔跳过ID。这似乎是一个残酷的事实,一个人要么容忍碰撞的机会(完全不可接受),要么容忍在某些边缘情况下跳过ID(令人讨厌,但不是世界末日)。

我会创建一个单独的表来保存下一个发票编号。添加新发票时,请确保更新下一个发票编号。把所有的东西都放在一笔交易中,你会有很好的状态。

剩下的问题(多个线程试图同时更新表)可以通过几种方式进行管理。至少从一开始,我最喜欢的是在下一个发票编号表中有一个时间戳列,并确保时间戳在更新过程中没有更改。