由于延迟,避免在 SQL 服务器中重复

Avoid duplicates in SQL server due the latency

我在 C# 中有一个类似 POS 的系统,很长一段时间它都没有出现任何问题(它只是一个 POS)。但现在有 4 个 POS 使用该系统,并连接到同一个数据库,一个 POS 的所有销售都去同一个审计 (table),其他所有销售都去那里。 所以在这个系统中这是程序

  1. 获取最后一个票号的函数(使用简单SELECT)
  2. 给那个数字加 1(下一个票号)。
  3. 生成一个 ID 代码,将此票号(带有终端、日期和员工代码)注入算法
  4. 将包含所有必要信息(日期、客户、员工、IDCode 等)的销售记录插入数据库(使用简单的 INSERT INTO)

但是有4个POS我发现有些销售有相同的Ticket number,幸运的是Ticket ID代码不一样,因为终端和员工不同,但如何避免这种情况?

编辑 1: 每个 POS 系统都有双重功能,在一种模式下 POS 销售是集中的,并且这种模式下的每个 POS 都会生成连续的票(就像它们都在一个 POS 中一样),在另一种模式下每个 POS 都有自己的票号,因此我无法使用身份。

您需要在原子操作中执行此操作。因此,您可以将所有内容包装在一个事务中并锁定 table。 See here 对锁定等进行很好的讨论。

锁定会减慢其他一切,因为一切都将开始等待 table 释放以完成它,这可能不是您可以忍受的事情。

或者您可以在将由数据库管理的列上使用identity并保持唯一的递增数字。

您还可以创建主键(希望您有一个)来组合几项内容。然后您可以为每个 POS 端点保留一个 运行 数字,以查看有关它们如何执行的更多数据。但这更多地涉及分析,这不在此处的范围内。

我强烈建议您尽可能放弃当前的方法,改用 GUID PK。

但是,我意识到在某些情况下无法进行重新设计(我们的场景与您在遗留数据库中描述的场景完全相同)。

在这种情况下,您可以使用 UPDLOCK table 提示结合插入命令安全地获取最大值,并使用 OUTPUT INSERTED 功能检索新的主键如果需要,将值转换为局部变量:

DECLARE 
  @PK Table (PK INT NOT NULL) 

INSERT 
  INTO Audit (
       TicketNumber, 
       Terminal,
       Date,
       EmployeeCode,
       Client,
       IDCode,
       ... other fields )
  /* Record the new PK in the tablevariable */
OUTPUT INSERTED.TicketNumber INTO @PK
SELECT IsNull(MAX(TicketNumber), 0) + 1, 
       @Terminal,
       @Date,
       @EmployeeCode,
       @Client,
       @IDCode,
       ... other values
  FROM Audit WITH (UPDLOCK)

DECLARE @TicketNumber INT

  /* Move the new PK from the local tablevariable into a local variable for subsequent use */
SELECT @TicketNumber = PK
  FROM @PK

如前所述,如果 TicketNumber 是连续且唯一的,那么 IDENTITY 字段听起来就是可行的方法。但是,如果由于某种原因有什么阻止了它,或者如果这一次需要太多更改,您可以通过使用 Application 在 ID 代码生成过程本身上创建一个锁来将过程本身限制为单线程锁(参见 sp_getapplock and sp_releaseapplock)。应用程序锁让您可以围绕任意概念创建锁。意思是,您可以将 @Resource 定义为 "generate_id_code",这将强制每个呼叫者等待轮到他们。它将遵循以下结构:

    BEGIN TRANSACTION;
    EXEC sp_getapplock @Resource = 'generate_id_code', @LockMode = 'Exclusive';

    ...current 4 steps to generate the ID Code...

    EXEC sp_releaseapplock @Resource = 'generate_id_code';
    COMMIT TRANSACTION;

您需要自己管理错误/ROLLBACK(如链接的 MSDN 文档中所述),因此放入通常的 TRY/CATCH。但是,这确实允许您管理情况。

请注意: sp_getapplock / sp_releaseapplock 应谨慎使用;应用程序锁绝对非常方便(例如在这种情况下),但只有在绝对必要时才应使用它们。

只需使用一个序列来生成下一个票号。

CREATE SEQUENCE Tickets
    START WITH 1
    INCREMENT BY 1;

然后每个POS就做

SELECT NEXT VALUE FOR Tickets;

序列保证永远不会 return 相同的数字两次。