SQL 程序错误地检查值是否存在
SQL Procedure incorrectly checks if value exists
我正在构建一个连接到 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 列表。
这是将查询发送到数据库的代码:
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 个查询包含数据,但每次都会执行。
我假设 id
列是一个 sql 标识列,对吧?
因为第一个连续的 7 个条目都是相同的,所以我认为您的应用程序是在多个线程上启动的,这些线程在开始时逐个执行,但后来它们的执行可能因为异常处理块的额外时间而发生分歧。这就是为什么只有第一条记录相乘的原因。
问题是您的存储过程不是线程安全的。 IF EXISTS(SELECT ...
没有在 dbo.FileTranslation
table 上放置锁,这在并行执行中可能会导致多个正在执行的存储过程发现所需的记录不存在并将继续执行 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...finally
或 using
模式 conn
处理!
我正在构建一个连接到 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 列表。
这是将查询发送到数据库的代码:
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 个查询包含数据,但每次都会执行。
我假设 id
列是一个 sql 标识列,对吧?
因为第一个连续的 7 个条目都是相同的,所以我认为您的应用程序是在多个线程上启动的,这些线程在开始时逐个执行,但后来它们的执行可能因为异常处理块的额外时间而发生分歧。这就是为什么只有第一条记录相乘的原因。
问题是您的存储过程不是线程安全的。 IF EXISTS(SELECT ...
没有在 dbo.FileTranslation
table 上放置锁,这在并行执行中可能会导致多个正在执行的存储过程发现所需的记录不存在并将继续执行 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...finally
或 using
模式 conn
处理!