为什么在使用.NET Core时SqlClient / SqlConnection查询(ExecuteScalar或ExecuteReader)中存在超时,而不是.NET常规或SSMS?

问题描述 投票:1回答:1

我正在尝试使用.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 of Exception消息中的详细信息:

enter image description here

c# entity-framework-core
1个回答
3
投票

由于参数嗅探,您在此处看到的内容似乎是一个错误的查询计划缓存条目,由于灾难性的查询计划导致超时。参数嗅探的问题在于,当没有现有的操作查询计划(与当前执行模式匹配)时,它会根据它看到的第一个参数值生成查询计划。如果您有严重偏差的数据,生成的查询计划可能适用于某些值,但对其他值则是灾难性的。例如,考虑具有一个值的3行和具有另一个值的300万行的场景。如果您根据“3行”值生成查询计划,它可能会针对该数量进行优化的决策 - 它可以在3,30和300可行的情况下正常工作 - 但是300万可能会崩溃。反之亦然。在Stack Overflow中,我们将其称为“Jon Skeet问题”:Jon(用户页面上排名第一的用户)与一个全新的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天内在半夜发生。

© www.soinside.com 2019 - 2024. All rights reserved.