【问题标题】:Why is there a timeout in SqlClient / SqlConnection query (ExecuteScalar or ExecuteReader) when using .NET Core, but not .NET regular or SSMS?为什么使用 .NET Core 时 SqlClient / SqlConnection 查询(ExecuteScalar 或 ExecuteReader)会超时,而不是 .NET 常规或 SSMS?
【发布时间】:2018-05-11 06:56:52
【问题描述】:

我正在尝试在 .Net Core 中使用以下代码执行 SP

using (DBContext context = new DBContext()){
                {
using (var command = context.Database.GetDbConnection().CreateCommand())
    {

        command.CommandText = "Sp_Name";
        command.CommandType = CommandType.StoredProcedure;
        command.Parameters.Add(new SqlParameter("@input", SqlDbType.VarChar ,3) { Value = InputValue });
        command.Parameters.Add(new SqlParameter("@Return_Value", SqlDbType.VarChar, 3) { Value = string.Empty });

        context.Database.OpenConnection();

        var dataReader = command.ExecuteReader();

        if (dataReader.Read())
        {
            var code = dataReader.GetString(dataReader.GetOrdinal(""));
        }
    }}

查询对某些输入参数工作正常,但对某些输入参数抛出异常,例如:

--此场景在 EF 代码和 SQL 中运行良好

    SP - exec Sp_Name @input = 'PDX',  @Return_Value = ''

    --Result (No Column Name) - '3I9' 

-- 这种情况在 EF 代码中不工作,但在 SQL 中工作正常

    SP - exec Sp_Name @input = 'N01',  @Return_Value = ''

    --Result (No Column Name)  - 'WE5'

异常信息

System.Data.SqlClient.SqlException (0x80131904): Timeout expired.  The timeout period elapsed prior to completion of the operation or the server is not responding. ---> System.ComponentModel.Win32Exception (0x80004005): The wait operation timed out
   at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
   at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose)
   at System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady)
   at System.Data.SqlClient.SqlDataReader.TryConsumeMetaData()
   at System.Data.SqlClient.SqlDataReader.get_MetaData()
   at System.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString)
   at System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean async, Int32 timeout, Task& task, Boolean asyncWrite, SqlDataReader ds)
   at System.Data.SqlClient.SqlCommand.ExecuteScalar()
   at Mednax.ReferringPhysician.Data.PdxService.getGPMSCode(String practiceCode) in C:\Work\GIT\ReferringPhysician2\Mednax.ReferringPhysician.WebAPI\Mednax.ReferringPhysician.Data\PdxService.cs:line 971
ClientConnectionId:199f2b1a-cb1b-4752-8632-9f2c54bcefd8
Error Number:-2,State:0,Class:11

堆栈跟踪:

   at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
   at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose)
   at System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady)
   at System.Data.SqlClient.SqlDataReader.TryConsumeMetaData()
   at System.Data.SqlClient.SqlDataReader.get_MetaData()
   at System.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString)
   at System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean async, Int32 timeout, Task& task, Boolean asyncWrite, SqlDataReader ds)
   at System.Data.SqlClient.SqlCommand.ExecuteScalar()

SP 如下所示:

SP -

(
@Input varchar(3),
@Return_Value varchar(3) output
)
AS

SET NOCOUNT ON
SET @Return_Value = NULL

SELECT  TOP 1   @Return_Value = pacl.P_Code
    FROM    TABLEA pacl with (nolock)
    LEFT OUTER JOIN TABLEB rpp with (nolock)
        ON  rpp.Code = pacl.Code
        AND rpp.P_Code = @Input
    WHERE   rpp.P_Code IS NULL
    ORDER BY pacl.P_Code

IF @@Rowcount = 0 SET   @Return_Value = '***'

Select @Return_Value

异常消息的 Targetsite 内部详细信息:

【问题讨论】:

  • 我强烈建议将错误消息和完整的堆栈跟踪作为文本而不是图像发布。另外,如果您知道只有一列,为什么不直接使用deatReader.GetString(0)
  • 哪一行失败了?你至少能告诉我们吗?另外:由于此代码看起来不涉及 EF...如果您只使用 SqlConnection 是否有效?
  • 您好,感谢您的评论,我已添加堆栈跟踪详细信息
  • @PradeepH 听起来像“参数嗅探”,然后 - 严重偏差数据的常见问题。我们能不能看到SP?另外,请注意,有一些 SET 选项(默认情况下)在 .NET 和 SSMS 之间是不同的 - 如果 SET 选项导致索引不可用,这可能会导致性能巨大差异.例如,“持久计算列”可能非常容易受到此问题的影响。
  • 对。所以第一个也是最简单的尝试是参数嗅探修复。尝试将OPTION ( OPTIMIZE FOR UNKNOWN ) 添加到select 主查询的底部(真正起作用的查询,而不是最后的查询),在order by 之后。看看有没有帮助。

标签: c# entity-framework-core


【解决方案1】:

您在这里看到的似乎是由于参数嗅探导致的错误查询计划缓存条目,由于灾难性查询计划导致超时。参数嗅探的问题在于它会根据 第一个参数值 生成一个查询计划,它在不存在操作的现有查询计划(与当前执行模式匹配)时看到。如果您有严重偏差的数据,则生成的查询计划对于某些值可能很好,但对于其他值来说是灾难性的。例如,考虑有 3 行具有一个值和 3 百万行具有另一个值的场景。如果您根据“3 行”值生成查询计划,它可能会针对该数量级优化决策 - 它适用于 3、30 甚至可能 300 - 但对于 3 百万可能会崩溃。反过来也一样。在 Stack Overflow,我们将其称为“Jon Skeet 问题”:Jon(用户页面上的 #1 用户)对全新的 1-rep 用户的数据分布非常不同,并且 Jon 的查询计划对于那个 1 -rep 用户,反之亦然。

幸运的是,SQL Server 有针对这种情况的查询提示:OPTIMIZE FOR/UNKNOWN。最简单的用法是将OPTION ( OPTIMIZE FOR UNKNOWN ) 添加到受影响的查询中;这指示它不要根据生成查询时看到的参数值来极大地偏差查询计划。如果只有一些参数有问题,您也可以指定单个参数(例如,@userId 对我们来说)。

所以;为什么这可能适用于 SSMS(查询分析器)和 .NET,但不适用于 .NET 核心?我认为这里的问题是不同的SET 选项。 various SET options 定义执行模式;其中一些选项可能会影响查询生成,因此对于具有不同SET 选项的两个客户端,可能需要单独的计划。这意味着 .NET Core 可能会有效地命中与 .NET 不同的查询计划缓存,因此:当一个正在工作时,另一个正在失败。但是:这并不意味着一个“更糟”;相反,它只是意味着其中一个碰巧针对导致灾难性计划的数据生成查询计划。当计划缓存由于某种原因(通常只是:逐渐的数据漂移)失效时,同样的问题也可能会产生影响——就像最笨拙的用户(等)正在使用该站点一样。参数嗅探问题通常不会立即出现 - 它们会在任何人部署任何东西 4 天后的半夜发生。

【讨论】:

    猜你喜欢
    • 2011-07-14
    • 1970-01-01
    • 1970-01-01
    • 2021-05-12
    • 1970-01-01
    • 1970-01-01
    • 2019-09-08
    • 2021-04-26
    • 1970-01-01
    相关资源
    最近更新 更多