【问题标题】:Avoid to insert same number by multiple users using transaction避免多个用户使用事务插入相同的号码
【发布时间】:2025-12-26 05:50:16
【问题描述】:

我有几个插入查询合并到事务范围中。插入的第一个是创建新的产品货号,该货号是通过将表中的最高值加一来构建的。不幸的是,我只是注意到,主要是在测试期间,例如,如果来自两个不同应用程序的两个用户单击触发我的交易方法的按钮,他们可以获得相同的新产品编号并插入它。我怎样才能避免这种情况?

我的部分交易查询如下:

Public Sub ProcessArticle(ByRef artikel As ArticlesVariations)
        Dim strcon = New AppSettingsReader().GetValue("ConnectionString", GetType(System.String)).ToString()
        Using connection As New SqlConnection(strcon)
            connection.Open()
            Using transaction = connection.BeginTransaction()
                Try

                            Using cmd As New SqlCommand("INSERT INTO tbArtikel (Nummer) VALUES (@Nummer);Select Scope_Identity()", transaction.Connection)
                                cmd.CommandType = CommandType.Text
                                cmd.Connection = connection
                                cmd.Transaction = transaction

                                 'Get next product number from table tbArtikel  (this will be new product number)'
                                 Dim NewArtNummer as String =  New DALArtikel().GetNewArtikelNumber(transaction)
                                 art.Nummer = NewArtNummer

                                cmd.Parameters.AddWithValue("@Nummer", art.Nummer)
'Get inserted product id for other diffrent inserts below'
 newArticleRowId = CInt(cmd.ExecuteScalar()) 

'....
other INSERTs queries to other tables ...
...'

       transaction.Commit()
                Catch ex As Exception
                    transaction.Rollback()
                    Throw 'Rethrow exception.'
                End Try
            End Using
        End Using
    End Sub

文章编号是varchar 列(我知道这很糟糕等),但这是它在当前范围内的实现方式,不可能更改它。

请注意,我不能在数据库级别进行任何更改,例如新表、触发器等(一个例外是在数字列上创建唯一索引)。我必须从应用程序级别实现。

我想到了三个选项,想听听您的建议:

第一个选项:

a) 将此列设为唯一列,这样当几个用户仅插入一个时,将插入该数字,但所有其余用户都会从捕获有关唯一键违规的错误中得到错误,因此我可以以某种方式管理它使信息重试。

b) 与 a) 相同,但隔离级别设置为 ReadUncommited - 从我的角度来看,这将让我知道,当下一个用户读取 newnumber 时,他们有机会从脏数字中读取(所以也来自第一个用户插入),因此他们有机会获得下一个数字。

第二个选项:

只是为了使事务隔离以读取未提交 - 但这并不能保证所有同一行用户都会获得新号码(可能是最糟糕的)

第三个选项:

听说过TABLOCK,但不知道在这种情况下是否可以考虑。

...还有别的吗?

目前我正在考虑使用第一个选项 -catch 错误并通知用户。

【问题讨论】:

  • SQL Server 的哪个版本?
  • @Shnugo 不知道,但肯定不少于 2008 年
  • 从 SQL Server 2012 开始有 SEQUENCE,但这在这里对您没有帮助。显然其他人也有同样的问题,因此发明了这个......
  • @Shnugo 如果这对我的选择没有帮助呢?

标签: sql-server vb.net tsql


【解决方案1】:

我关注了您的一些较早的问题...这显然不是那么容易解决的。所以我有一个完全不同的想法:

由于您的 ProductNumber 是 VARCHAR,您可以在那里写任何您想要的东西吗?

是否可以在插入时将GETDATE() 设置为字符串文字(ISO 8601)到此列并稍后执行UPDATE 语句,这将根据顺序将值重新设置为正确的编号插入?

反正又奇怪又丑……

【讨论】:

  • 是的,我认为我可以,但您知道本专栏很有趣,但我们是否必须使用 GETDATE 解决方案使其更有趣 :) ?嗯无论如何你能展示你得到了什么吗? P.S 我认为考虑第一个选项,这将保证我是独一无二的,所以没有重复的数字,只是为所有其他用户处理错误。
  • @JimmyJimm 好吧,奇怪的限制需要奇怪的建议 :-) 你也可以放置一个简单的 GUID 字符串,只要有 InsertTime 列可以排序。这样的 GUID 可以在您的应用程序中创建,您可以在您的流程中使用它作为该行的唯一键。关于您的第一个选项:是的,您可能会这样做......不太可能出现错误,用户不会经常看到这个......
  • 您的想法也不错,我喜欢它,并且会三思而后行,但我想我会选择第一个选项,因为现在我知道最多有 30 个用户在使用此应用程序版本。所以这个错误只有在最少两个用户同时点击按钮时才会发生,所以只有一个会成功,但这种情况很少发生。对?关于这个唯一的,当我使用 ALTER TABLE T_Artikel WITH CHECK ADD CONSTRAINT UQ_T_Artikel_Nummer UNIQUE (Nummer) 时,其他用户无法插入对吗?我要求确认,因为我不是数据库专家,只是为了确定。