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

Posted

技术标签:

【中文标题】SQL 过程错误地检查值是不是存在【英文标题】:SQL Procedure incorrectly checks if value existsSQL 过程错误地检查值是否存在 【发布时间】:2021-05-01 05:04:18 【问题描述】:

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

这是生成查询的代码:

    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

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

.

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

.

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

【问题讨论】:

Please do not upload images of code/errors when asking a question. 警告: 您的代码图像显示您的应用程序很容易受到注入攻击。 切勿将未经处理的值注入您的 SQL 语句。 参数化你的陈述。 这符合预期吗? UPDATE dbo.FileTranslation SET fName = @fName, Printer = @Printer -- MISSING WHERE??????? sqlperformance.com/2020/09/locking/upsert-anti-pattern 当您的存储过程发生错误时,您会吃掉错误。过程的调用者没有逻辑失败的想法。 【参考方案1】:

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

问题在于您的存储过程不是线程安全的。 IF EXISTS(SELECT ... 没有在dbo.FileTranslation 表上加锁,这在并行执行时可能会导致多个正在执行的存储过程发现所需的记录不存在,并将继续使用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 处理!

【讨论】:

谢谢,在我的更新中结合使用 WHERE 子句和 WITH (UPDLOCK,SERIALIZABLE)

以上是关于SQL 过程错误地检查值是不是存在的主要内容,如果未能解决你的问题,请参考以下文章

SQL 性能 - 更好地插入和引发异常或检查是不是存在?

如何根据 PL/SQL 变量值检查输入参数值是不是存在作为列表?

如何检查sql的第一列或第二列中是不是存在一个特定值?

SQL 检查分区中是不是存在值,使用 CASE WHEN 没有任何 JOIN

TRY/CATCH 块与 SQL 检查

检查架构中的 IF 表 EXISTS 时发生 Oracle PL/SQL 过程错误 [重复]