为什么我的SqlCommand返回一个字符串而不是整数?

4
我有一个查询应该总是返回一个 int 值。但是我发现它返回了一个与预期完全无关的字符串。
我们遇到了一些随机的 FormatExceptions,这些异常与几个数据库查询有关。经过进一步的日志记录,我发现在今天早上,下面的查询返回了字符串 "gladiator"。Website.PkID 是一个 int 列,大多数情况下都能正常工作,但有时会失败,并返回一个比任何有效 WebsiteID 都要大得多的 int,或者是一个随机字符串。
这个特定的查询每次会话开始时都会被调用一次。它没有使用共享连接,所以我很难理解它为什么会得到如此混乱的结果。连接池中可能存在某种类型的损坏吗?
我认为问题不仅限于这个查询。我还看到类似的 FormatExceptions(由于意外的结果)来自于 LINQ 查询。我们也在同一时间发现了一些这样的错误:
“在向服务器发送请求时发生传输级错误。(提供程序:TCP 提供程序,错误:0 - 远程主机强制关闭了现有的连接。”
这可能是连接问题吗?或者我们在数据库服务器和 Web 服务器之间混淆了结果集?这让我非常困惑。
有问题的查询:
public static int GetActiveWebSiteID(string storeID, string statusID)
{
    int retval;

    string sql = @"SELECT isnull(MAX(PkID),0) FROM WebSite 
                   WHERE StoreID = @StoreID 
                   AND WebSiteStatusID = @WebSiteStatusID";

    SqlConnection conn = new SqlConnection(Settings.ConnString);
    SqlCommand cmd = new SqlCommand(sql, conn);
    cmd.CommandType = CommandType.Text;
    cmd.Parameters.AddWithValue("@StoreID", (object)storeID ?? DBNull.Value);
    cmd.Parameters.AddWithValue("@WebSiteStatusID", (object)statusID ?? DBNull.Value);

    conn.Open();
    using(conn)
    {
        var scalar = cmd.ExecuteScalar(); // <-- This value returned here should only ever be an int, but randomly is a string

        retval = Convert.ToInt32(scalar);
    }
    return retval;
}

上述查询在最近几年一直运行良好,但现在我们的应用程序中有一堆额外的LINQ查询(不确定是否有影响)。我们正在运行.Net 3.5。


当'scalar'是一个字符串时,它的值是什么? - Richard Szalay
当PkID的MAX值为null时,可能是字符串"0"。 - TheVillageIdiot
如上所述,我在错误日志中看到的字符串是“gladiator”。我有一种预感,这可能与系统中其他查询结果混淆了,特别是关键字搜索。但我还无法证明这一点。 - Chad Gilbert
我从来没有见过结果集出问题。日志记录可能有 bug 吗? - dotjoe
9个回答

9
在几个月的忽略后,随着流量逐渐增加,这个问题开始达到临界质量。我们增加了日志记录,并发现许多明确的情况,在重载下,完全不同的结果集将返回到不相关的查询中。
我们在分析器中观察查询并能够看到坏结果总是与相同的 spid 相关联,每个坏结果总是比实际的 SQL 语句查询少一个查询。就像错过了一个结果集一样,然后从 spid 中返回下一个结果集(来自同一池中的另一个连接)。非常疯狂。
通过试错,我们最终找到了一些 SqlCommand 或 LINQ 查询,它们的 SqlConnection 没有立即在使用后关闭。相反,通过一些源自对 LINQ 连接的误解的松散编程,DataContext 对象仅在请求结束时而不是立即处置(和连接关闭)。
一旦我们将这些方法重构为使用 C# 的“using”块立即关闭连接(释放该池以供下一个请求使用),我们就不再收到错误消息了。虽然我们仍然不知道连接池会出现混乱的根本原因,但我们能够停止所有这种类型的错误。这个问题是和我发布的另一个类似错误一起解决的,可以在此找到链接:什么导致“内部连接致命错误”?

我想知道你是否找到了上述问题的根本原因?我遇到类似的问题(例如,请求已经存在多年的配置表条目时返回空字符串,或者像在这里描述的连接掉线:https://dev59.com/EXM_5IYBdhLWcg3w9oMA)。快速“关闭”连接听起来像是一种解决方法。不应该在不同的SqlConnection实例之间混合数据,对吗? 如果你能详细说明一下就太感激了 :) - Kuba Wyrostek
@KubaWyrostek - 这是13年前的事情,我们从未发现根本原因。我怀疑底层连接池驱动程序中存在某些错误,在压力下崩溃,并做了不可思议的事情:将其他查询的结果交给不同的调用者。我希望现代驱动程序已经解决了这个问题,但我个人在过去十年中没有见过这种情况! - Chad Gilbert
1
@KubaWyrostek - 微软刚刚披露了现代 SqlClient 实现中的一个漏洞(https://github.com/advisories/GHSA-8g2p-5pqh-5jmc),这可能是您观察到的问题的根本原因,并且更新到最新的库版本应该可以解决它:“在 System.Data.SqlClient 和 Microsoft.Data.SqlClient 库中存在漏洞,在高负载下发生超时可能会导致异步执行的查询返回不正确的数据作为结果。” - Chad Gilbert

1

最近我看到一个代码出现了意外切换连接字符串的情况。为了诊断问题,请硬编码连接字符串并查看问题是否消失。

此外,为了保持清晰,使用嵌套的using块:

using(SqlConnection conn = new SqlConnection("hard-coded connection string"))
{
    using (SqlCommand cmd = new SqlCommand(sql, conn))
    {
        // more init
        object scalar = cmd.ExecuteScalar();

        // process result
    }
 }

“如果有两个数据库实例,一个里面的PkID是int类型,而另一个则是varchar类型,那我也不会感到惊讶。”

使用SQL Profiler查看是否可以捕获“gladiator”的返回结果。在我处理的另一种情况下,SQL Profiler根本没有显示任何内容,表明实际查询正在发送到不同的数据库。


只有一个带有网站表的有效数据库。清理一下using可能是个好主意 - 这不会有任何坏处。 - Chad Gilbert
我猜你知道这个,因为你使用了Profiler并看到了返回“gladiator”的调用? - John Saunders

1

我认为你在考虑sqlCommand.ExecuteNonQuery,它返回一个int值,表示受影响的行数...

这是ExecuteScalar方法的定义:

public override object ExecuteScalar()
Member of System.Data.SqlClient.SqlCommand

摘要:

执行查询,并返回查询结果集中第一行的第一列。其他列或行将被忽略。

返回值:

结果集中第一行的第一列,如果结果集为空,则返回 null 引用(在 Visual Basic 中为 Nothing)。

因此,我认为通常以列值的字符串表示形式返回该列是一种常见方式。


ExecuteScalar() 正是我想要的。我不关心行数。 - Chad Gilbert

0

ExecuteScalar()函数的返回类型是object,并且您使用var关键字声明结果变量。这不是一个很好的组合,因为您需要系统正确推断类型。


该列是一个整数,结果却是一个字符串"gladiator"。我不明白类型推断与此有何关系。一个整数列或任何聚合列都不应该返回一个字符串。 - Chad Gilbert
2
类型推断永远不应该产生“角斗士” :) - VVS
2
除非PkID不是一个int列,否则他发布的代码也不应该被采纳。 - Joel Coehoorn
编译器要么精确解析类型,要么给出模糊错误。 - VVS
1
但在这种情况下,你怎么知道它解析为你期望的类型?Convert.ToInt32() 几乎可以接受任何东西,而那是它唯一使用的地方。 - Joel Coehoorn

0

"PkID"这个字段是位于"WebSite"表中的varchar/char类型。

如果查询中的"ISNULL"部分返回真,则它将返回一个整数(0),否则它将返回一个字符串,其值为"PkID"。


正如问题中所提到的,Website.PkID 是一个整数列。 - Chad Gilbert

0

可能会有多个WebSite表。您能用模式名称限定表吗:

SELECT isnull(MAX(PkID),0) FROM YourSchema.WebSite WHERE StoreID = @StoreID AND WebSiteStatusID = @WebSiteStatusID


只有一个网站表。 - Chad Gilbert

0

当方法无法返回一个 int 值时,这是否有共同点?

由于您的查询始终只返回单个行中的单个列,如果使用更加类型安全的 ExecuteReader 并取第一列的值,会得到什么结果?

它是否总是返回一行?如果 WHERE 子句导致不返回任何行(例如参数与您想象的不同),则 ISNULL 不会生效 - 完全没有行,而 ExecuteScalar 应该返回 NULL。


为了简洁起见,我在上面的示例中剥离了一些代码。实际代码具有更多的日志记录,并且在尝试将其转换为int之前,还会检查“scalar”的值是否为null和DbNull。这些检查处理了无结果的情况。 - Chad Gilbert
但是正如您建议的那样,我会尝试重新编写查询语句,看看是否有所帮助。 - Chad Gilbert

0

我认为你发布的查询和LINQ都不是问题所在。

你确定你正在查看正确的源代码吗?方法叫什么?日志记录如何完成?

选择没有出错。


是的,这是正确的源代码。为了简洁起见,我已经从上面的示例代码中删除了log4net日志记录,并注入了非常具体的日志记录,告诉我输入和输出。我有一种预感,在SQL服务器和Web服务器之间可能存在某种网络问题。我们以前遇到过奇怪的问题,但从未达到这种严重程度。 - Chad Gilbert
你没有说服我。当你使用SQL Profiler并在实际运行代码的服务器上看到它运行时,我们才会知道它运行在哪个服务器上。在那之前,你只是在猜测。 - John Saunders

0

我假设Settings.ConnString是从Web.Config或者注册表读取,并且在其他静态程序中被重复使用。可能存在一个时间问题,即您的程序在修改cmd.CommandText的同时,并发执行了第二个方法,然后再调用cmd.ExecuteScalar()吗?

希望这有所帮助。

Bill


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接