我们正在开发小型企业使用的多租户系统,需要为其客户生成发票。基本上每个租户出于显而易见的原因,都希望自己的发票号码序列彼此独立生成。因此,第一个租户可以拥有发票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;
答案 0 :(得分:1)
我会创建一个单独的表来保存下一个发票编号。添加新发票时,请务必更新下一个发票编号。保持交易中的所有内容,您将处于良好的状态。
可以通过几种方式管理剩余的问题(多个线程同时尝试更新表)。我最喜欢的,至少在开始时,是在下一个发票号码表中有一个Timestamp列,并确保时间戳在更新过程中没有改变。
答案 1 :(得分:1)
为了支持每秒最高的交易次数,我认为您最好的答案是在发票表中放置一个唯一的索引(租户ID,发票ID)并执行并发插入,然后在唯一键的情况下违规,重试。
我的基础是几年前我读到的关于在这种情况下实现最高吞吐量的文章。
无论如何,我强烈建议不要每个租户有一张桌子。您可能会查看序列,但我不确定每个租户是否有一个序列是合理的。
“最后一张发票”表格,每个租户可以工作一行:
UPDATE dbo.TenantLastInvoice
SET
LastInvoiceID = LastInvoiceID + 1,
@InvoiceID = InvoiceID
WHERE TenantID = 123;
我不是100%确定您是否需要锁定提示,您可以考虑ROWLOCK
,UPDATELOCK
,如果所有其他方法都失败,READPAST
(如果没有更新,则重试)
不要期望能够完全避免偶尔跳过ID。似乎一个事实是,要么容忍碰撞的机会(完全不可接受),要么容忍在某些边缘情况下跳过ID(令人讨厌,但不是世界末日)。