【问题标题】:Same data is inserted during insert在插入期间插入相同的数据
【发布时间】:2016-09-21 18:05:28
【问题描述】:

我有几个插入查询合并在事务中。第一个插入是创建新的产品文章编号,将表中最高的产品编号递增一。不幸的是,我只是注意到mostly 在测试期间,例如,如果来自两个不同应用程序的两个用户单击触发我的交易方法的按钮,他们可以获得相同的新产品编号。怎样才能避免这种情况?是否有类似锁定第一次插入的东西,这样如果第一个用户访问表以插入限制其他用户的插入,那么他们必须在第一个用户插入完成后在队列中等待?有没有类似的东西?此外,我认为如果有人插入其他用户无法插入。我用你理解的代码制作了 cmets。

我的部分交易查询如下:

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

                    For Each kvp As KeyValuePair(Of Integer, Artikel) In artikel.collection
                        articleIndex = kvp.Key
                        Dim art As Artikel = kvp.Value

                            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

【问题讨论】:

  • 为这些东西分配某种独特的价值与 AutoIncrement PK 没有太大不同。您需要找到一种在插入行时分配值的方法。由于它显然是一个具有 3 个值的字符串,因此可能是一个存储过程。尤其是在也是一个交易的循环中,您将有很多欺骗
  • @Plutonix 嗨,Plutonix,有没有办法从我展示的方法中做到这一点?
  • 我不知道DALArtikel().GetNewArtikelNumber(transaction) 做了什么,但大概它会查看数据库中的行,执行某种 Max()+1。如果另一个用户正在做同样的事情,他/她将获得相同的值。
  • @Plutonix 这背后还有更多,但我可以说 - 是的 - 这是一种方法,它正在做一些事情,比如从文章表中获取最大值并增加它,这样我就会得到新的数字。 P.S 我认为这不是关于插入语句本身,而是因为 NewArtNummer 过程对吗?
  • 由于它在事务中,所有这些计算在您提交之前都是暂定的 - 让其他用户有更多时间做同样的事情并获得相同的数字。

标签: sql-server vb.net tsql


【解决方案1】:

确保不会为用户分配相同值的唯一方法是在插入行时从服务器发出它们。这是服务器为 PK 发布 AI 值背后的全部前提。

但是因为你的东西是一个多段的“数字字符串”,它会出现问题。而不是撕开字符串以在字符串的 parts 上使用 WHERE 子句找到一个段的 Max()+1。考虑这样的事情:

从用于递增和发出值的表开始:

 {DocId Int, SegmentB int, SegmentC Int}

这将简单地跟踪要在 other 表中使用的值。然后是创建/增加新代码的存储过程(MySQL - 这是一个概念性答案):

CREATE DEFINER=`root`@`localhost` PROCEDURE `GetNextProductCode`(in docId int,
        in Minr int, 
        in Rev int 
        )
BEGIN
    SET @maxR = 0;
    SET @retCode ='';

    if Minr =-1 then
        Start transaction;
        SET @maxR = (SELECT Max(SegmentB) FROM articlecode WHERE MainId = docId) + 1;
        UPDATE articlecode SET SegmentB = @maxR WHERE MainId = docId;
        Commit;
        Select concat(Cast(docId As char) , '.', 
                      Cast(@maxR AS char) , '.',
                      Cast(Rev As char)
                      );
    end if;
END

这是一个粗略的过程概念。因此,它仅适用于第二个段(我不知道创建新 SegmentB 时会发生什么 - SegmentC 是否重置为 1 ???)。这个想法是:

  • 传递数字,因此无需撕掉字符串
  • 为需要下一个值的段传递-1
  • sp 获取 Max()+1 并更新计数器表,以便下一个用户将获得一个新值
  • 如果由于某种原因您最终没有保存该行,则会出现空白
  • sp 使用事务(可能只需要保护更新),因此一次只能发生 1 次更新
  • 返回新代码。它可能只返回 2 个值,但无论如何你都要将它们粘合在一起

有很多事情要做:

  • 它只适用于 SegmentB
  • 对于新的 DocId (-1),插入具有 10001(?) 默认值的新行
  • 对于新的 segmentB(无论它是什么)也是如此:为该 DocId 插入一个具有默认值的新行

要在插入一行之前获取新代码:

cmd.CommandType = CommandType.StoredProcedure

cmd.Parameters.Add("docId", MySqlDbType.Int32).Value = 3
cmd.Parameters.Add("Minr", MySqlDbType.Int32).Value = -1
cmd.Parameters.Add("Rev", MySqlDbType.Int32).Value = 1

dbcon.Open()
Using rdr = cmd.ExecuteReader()
    rdr.Read()
    Console.WriteLine(rdr(0))
End Using

明显的缺点是每次插入都需要您点击数据库才能...很好地保存到数据库。如果它们是 int 值,则可能是触发器。

【讨论】:

    【解决方案2】:

    我是一名 SQL 开发人员,我的 VB 技能已经过时了大约 15 年,但与其在 VB 中自己创建递增的数字,不如让 SQL 使用 IDENTITY 字段生成它们。 SQL 永远不会允许重复,然后您只需要返回 SCOPE_IDENTITY():

    ALTER TABLE dbo.tbArtikel
    ADD [ArtikelID] INT IDENTITY(1,1) PRIMARY KEY;
    

    【讨论】:

    • Fox 产品编号只是 varchar 字段...(我知道它不好等,但它是怎么回事)
    • 哎哟。不过,可能是相同的解决方案:只有一个 IDENTITY 字段,获取该值,将其转换为 VARCHAR,然后将其存储在产品编号字段中。不理想,但在这样的多用户环境中更安全。
    • 不幸的是,在当前的实现中我不能这样做,有没有我可以部署到我的事务或类似的东西的 lcok?
    • 我不明白每个用户如何在他们自己的事务中运行。也许您可以通过 GUID 获得创意?至少 .Net 通常会生成一个唯一的 GUID,但我不确定您如何将其用于现有结构。
    • 在 SQL 中,您可以使用 TABLOCK 表提示来保持对该表的锁定,以防止其他人使用它(请参阅stackoverflow.com/questions/3662766/…)。我只是不知道 .net 如何处理 SQL 事务,抱歉。
    【解决方案3】:

    我有两个建议:

    第一个建议:将您的代码移动到存储过程中,这样您的所有用户都将执行相同的事务,您可以按照自己的方式设置隔离级别。阅读This

    第二个建议:我会在你的字段 Nummer 上创建一个唯一索引。这样,当我尝试插入重复值时,它会引发一个错误,我可以通过告诉用户他需要重试相同的操作或自动重试来处理它。

    不建议尝试为您的操作锁定记录或表,但是您可以在代码项目上查看此article,您可能会找到所需的内容。如果您的程序在事务中间停止,请确保提供一种释放所有锁的机制。

    【讨论】:

    • 两个问题:第一个:我可以为 varchar 类型的字段创建唯一索引吗?第二:拥有存储过程——即使有多个用户调用它——这是否意味着他们都将在同一个事务上工作,而不是每个用户都拥有自己的事务?怎么可能?
    • 第一个:是的,如果表中没有重复值或空值,您可以在 varchar 列上创建唯一索引。 2nd:我相信是的,你可以使用 SET Transaction Isolation Level 将事务隔离级别设置为最适合你的设置。请阅读关于我的第一个建议的文章,了解不同隔离级别的详细说明。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-05-16
    • 2017-09-09
    • 2017-06-03
    • 1970-01-01
    • 2021-03-22
    • 1970-01-01
    • 2017-09-01
    相关资源
    最近更新 更多