如何重新启动按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)设置一个唯一索引,并进行并发插入,然后在违反唯一密钥的情况下重试。
我基于几年前读到的一篇关于在这种情况下实现最高吞吐量的文章。
无论如何,我强烈建议不要每个租户都有一张桌子。你可能会研究序列,但我不确定每个租户有一个序列是否合理。
每个租户有一行的"最后一张发票"表可以工作:
UPDATE dbo.TenantLastInvoice
SET
LastInvoiceID = LastInvoiceID + 1,
@InvoiceID = InvoiceID
WHERE TenantID = 123;
我不能100%确定您是否需要锁定提示,您可以考虑ROWLOCK
、UPDATELOCK
,如果其他所有操作都失败,则考虑READPAST
(如果没有更新,则重试)。
不要期望能够完全避免偶尔跳过ID。这似乎是一个残酷的事实,一个人要么容忍碰撞的机会(完全不可接受),要么容忍在某些边缘情况下跳过ID(令人讨厌,但不是世界末日)。
我会创建一个单独的表来保存下一个发票编号。添加新发票时,请确保更新下一个发票编号。把所有的东西都放在一笔交易中,你会有很好的状态。
剩下的问题(多个线程试图同时更新表)可以通过几种方式进行管理。至少从一开始,我最喜欢的是在下一个发票编号表中有一个时间戳列,并确保时间戳在更新过程中没有更改。