【问题标题】:How to Restart Column Increment Automatically Per ID?如何根据 ID 自动重新启动列增量?
【发布时间】:2016-03-19 10:46:30
【问题描述】:

我们正在开发小型企业使用的多租户系统,需要为其客户生成发票。基本上,每个租户出于显而易见的原因都希望自己的发票编号序列彼此独立生成。所以第一个租户可以有发票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;

【问题讨论】:

  • “出于显而易见的原因”。它们对我来说并不明显。也就是说,发票编号是由数据库生成的任意(但保证是唯一的)标识符。通过添加此要求,您实质上是在说最简单的任意形式(标识值或序列)还不够任意,因此您将增加操作/技术复杂性以获得我很难证明的收益。
  • 我了解您的来历,但我应该更清楚,而不是仅仅说“出于明显的原因”。这很明显,因为它是产品客户提出的业务需求。对他们来说,更实际的是知道他们的发票编号是根据自己公司的销售额而不是外部实体的销售额而增加的。

标签: c# sql-server entity-framework


【解决方案1】:

为了支持每秒最高的事务,我相信你最好的答案是在发票表中(租户ID,发票ID)上放置一个唯一索引并进行并发插入,然后在唯一键的情况下违规,重试。

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

无论如何,我强烈建议不要为每个租户使用一张桌子。您可能会查看序列,但我不确定每个租户有一个序列是否合理。

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

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

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

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

【讨论】:

    【解决方案2】:

    我会创建一个单独的表来保存下一个发票编号。添加新发票时,请确保更新下一个发票编号。将所有内容都保存在交易中,您的状态将非常好。

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

    【讨论】:

    • 在这里处理一个新项目,我们将采用这种方法 - 我很好奇实现您答案最后一部分的最佳方法,“在下一个发票号中有一个时间戳列表并确保时间戳没有作为更新的一部分而改变”。现在我正在选择最新的发票编号,然后添加具有该编号的发票,然后迭代发票编号,然后提交交易。时间戳检查的最佳位置在哪里?
    • 读取发票号时,获取时间戳。当您迭代发票编号时,请先阅读时间戳以确保它没有更改。使用此流程解决此问题的另一种方法是在选择号码之前开始交易;确保事务的隔离级别设置为可重复读取。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-07-27
    • 2018-08-06
    • 2016-12-08
    • 1970-01-01
    • 1970-01-01
    • 2011-03-02
    • 2018-01-22
    相关资源
    最近更新 更多