SQL 过程错误地检查值是否存在

问题描述

我正在构建一个连接到 sql 数据库的 Windows 窗体应用程序。 在我的应用程序启动时,它会向数据库发送一些查询以比较值:

Query list

这是生成查询代码

    private void CreateInsertQuery(DirectoryInfo source,string Printer)
    {
         foreach (FileInfo file in source.GetFiles())
        {
            queries.Add("EXECUTE sqlp_UpdateInsertFiles '"+ file.Name +"','" + Printer + "'"); 
        }
         foreach (DirectoryInfo folder in source.GetDirectories())
        {
            queries.Add("EXECUTE sqlp_UpdateInsertFiles '" + folder.Name + "','" + Printer + "'");
            CreateInsertQuery(folder,Printer); 
        }
    }

queries一个公共列表。

这是将查询发送到数据库代码

   public bool InsertQueries()
    {
        con.open(); 
        using(OleDbTransaction trans = con.BeginTransaction())
        {
            try
            {
                OleDbCommand cmd;
                foreach (string query in queries)
                {
                    try
                    {
                        cmd = new OleDbCommand(query,con,trans);
                        cmd.ExecuteNonQuery();
                    }
                    catch (Exception ex)
                    {
                        if (ex.HResult != -2147217873)
                        {
                            MessageBox.Show(ex.Message);
                        }
                    }
                }
                trans.Commit();
                con.Close();
                return true;
            }
            catch (Exception ex)
            {
                trans.Rollback();
                con.Close();
                return false;
            }
        }

    }

在我的 sql 数据库中,我创建了一个存储过程,该过程在数据库收到查询时被调用

    AS
        BEGIN
            BEGIN TRANSACTION;
            SET NOCOUNT ON;
            BEGIN TRY
                IF EXISTS
                   (SELECT TOP 1 fName,Printer
                    FROM   dbo.FileTranslation
                    WHERE  fName = @fName AND Printer = @Printer)
                BEGIN
                    UPDATE dbo.FileTranslation
                        SET fName = @fName,Printer = @Printer
                END;
                ELSE
                BEGIN
                    INSERT INTO dbo.FileTranslation(fName,Printer) VALUES (@fName,@Printer);
                END;
            COMMIT TRANSACTION;
            
            END TRY
            BEGIN CATCH
                IF @@TRANCOUNT > 0
                BEGIN
                    ROLLBACK TRANSACTION;
                END
            END CATCH
        END;
GO

当我在空数据库上运行我的应用程序时,这些值将毫无问题地添加

DataBase's table: first start-up

我也没有遇到任何错误。只有当我第二次启动我的应用程序时,才会在 IF EXISTS 上检查前两个查询。因为它仍在将数据插入我的数据库,准确地说是 5 倍。

Database filled with duplicates

这很奇怪,因为只有 2 个查询包含数据,但每次都会执行。

解决方法

我假设 id 列是一个 sql 标识列,对吗? 因为第一个连续的 7 个条目都是相同的,我认为您的应用程序是在多个线程上启动的,这些线程一开始是逐头执行的,但后来它们的执行出现分歧,可能是因为异常处理块的额外时间。这就是为什么只有第一条记录相乘。

问题在于您的存储过程不是线程安全的。 dbo.FileTranslation 没有在 IF EXISTS(SELECT ... 表上放置锁,这在并行执行时可能会导致多个正在执行的存储过程发现所需的记录不存在并继续执行 INSERT 分支的情况。

应用 https://dba.stackexchange.com/questions/187405/sql-server-concurrent-inserts-and-deletes 线程中的答案这可能对您有用:

...
IF EXISTS
   (SELECT TOP 1 fName,Printer
    FROM   dbo.FileTranslation WITH (UPDLOCK,SERIALIZABLE)
    WHERE  fName = @fName AND Printer = @Printer)
...

PS:与您的问题无关,但请注意@Lamu 对 SQL 注入的评论,并使用 try...finallyusing 模式为您进行 conn 处理!