异步查询数据库怎么能花0个节拍呢?

本文关键字:0个 查询 数据库 怎么能 异步 | 更新日期: 2023-09-27 18:15:34

我试图使用IDbInterceptor来计时实体框架的查询执行,尽可能准确,实现乔纳森·艾伦对类似问题的回答的变体:

public class PerformanceLogDbCommendInterceptor : IDbCommandInterceptor
{
    static readonly ConcurrentDictionary<DbCommand, DateTime> _startTimes =
            new ConcurrentDictionary<DbCommand, DateTime>();
    public void ReaderExecuted(DbCommand command,
            DbCommandInterceptionContext<DbDataReader> interceptionContext)
    {
        Log(command, interceptionContext);
    }
    public void NonQueryExecuted(DbCommand command,
            DbCommandInterceptionContext<int> interceptionContext)
    {
        Log(command, interceptionContext);
    }
    public void ScalarExecuted(DbCommand command,
            DbCommandInterceptionContext<object> interceptionContext)
    {
        Log(command, interceptionContext);
    }
    private static void Log<T>(DbCommand command,
            DbCommandInterceptionContext<T> interceptionContext)
    {
        DateTime startTime;
        TimeSpan duration;
        if(!_startTimes.TryRemove(command, out startTime))
        {
            //Log exception
            return;
        }
        DateTime now = DateTime.UtcNow;
        duration = now - startTime;
        string requestGUID = Guid.Empty.ToString();
        var context = interceptionContext.DbContexts.SingleOrDefault();
        if (context == null)
        {
            //Log Exception
        }
        else
        {
            var businessContext = context as MyDb;
            if (businessContext == null)
            {
                //Log Exception
            }
            else
            {
                requestGUID = businessContext.RequestGUID.ToString();
            }
        }
        string message;
        var parameters = new StringBuilder();
        foreach (DbParameter param in command.Parameters)
        {
            parameters.AppendLine(param.ParameterName + " " + param.DbType
                + " = " + param.Value);
        }
        if (interceptionContext.Exception == null)
        {
            message = string.Format($"Database call took"
                + $" {duration.TotalMilliseconds.ToString("N3")} ms."
                + $" RequestGUID {requestGUID}"
                //+ $" 'r'nCommand:'r'n{parameters.ToString() + command.CommandText}");
        }
        else
        {
            message = string.Format($"EF Database call failed after"
                + $" {duration.TotalMilliseconds.ToString("N3")} ms."
                + $" RequestGUID {requestGUID}"
                + $" 'r'nCommand:'r'n{(parameters.ToString() + command.CommandText)}"
                + $"'r'nError:{interceptionContext.Exception} ");
        }
        if (duration == TimeSpan.Zero)
        {
            message += $" 'r'nTime: start: {startTime.ToString("hh:mm:ss fffffff")}"
                + $" | now: {now.ToString("hh:mm:ss fffffff")}"
                + $" 'r'n 'r'nCommand:'r'n"
                + $"{parameters.ToString() + command.CommandText}";
        }
        System.Diagnostics.Debug.WriteLine(message);
    }

    public void NonQueryExecuting(DbCommand command,
            DbCommandInterceptionContext<int> interceptionContext)
    {
        OnStart(command);
    }
    public void ReaderExecuting(DbCommand command,
            DbCommandInterceptionContext<DbDataReader> interceptionContext)
    {
        OnStart(command);
    }
    public void ScalarExecuting(DbCommand command,
            DbCommandInterceptionContext<object> interceptionContext)
    {
        OnStart(command);
    }
    private static void OnStart(DbCommand command)
    {
        _startTimes.TryAdd(command, DateTime.UtcNow);
    }
}

奇怪的是,每10个左右的查询,它需要0个tick来执行。它似乎只发生在我异步运行它时,同时有几个查询。另一件需要注意的事情是,当我再次查询相同的查询时,并不总是相同的查询需要0个滴答。

此外,我目前正在测试的数据库位于本地网络上,而不是本地机器上- ping时间为0-1ms -所以即使数据被缓存,我也看不出它如何花费0个ticks。

值得注意的是,大多数查询花费的时间可疑地接近1,2,3 ms(例如0.997ms到1.003ms)。对我来说,这听起来像是操作系统旋转线程cpu时间和/或1ms睡眠。我不介意它发生了,但我只是想知道为什么,这样我就可以解释结果中的不准确之处了。

这可能与ConcurrentDictionary有关。但是当我现在测试时,我目前只调用异步(WCF)方法一次,等待每个异步DB调用,所以它甚至不应该一次触发更多的调用,据我的理解。这是一个叫做:

的例子
public async Task<IEnumerable<DTJobAPPOverview>> GetJobOverviewAsync()
    ...
    var efResponsibleUserFullName = await dbContext.tblUsers.Where(u =>
                u.UserID == efJob.ResponsibleUserID
            ).Select(u => u.FullName)
            .FirstOrDefaultAsync();
    dtJob.ResponsibleUserName = efResponsibleUserName;
    var efCase = await dbContext.tblCases.FirstOrDefaultAsync(c =>
            c.ID == efJob.FK_CaseID);
    dtJob.Case = Mapper.Map<DTCase>(efCase); //Automapper
    ...
}
顺便说一下,我知道我可能应该转换整个应用程序来使用导航属性,但这是我们目前所拥有的,所以请原谅我。

异步查询数据库怎么能花0个节拍呢?

向您的网络管理员致敬-看到如此低延迟的网络是相当罕见的。

DateTime.UtcNow具有与系统计时器相同的分辨率(这并不奇怪,因为系统计时器更新当前时间:))。默认情况下,在Windows NT上,这是10ms -所以在一个干净的系统上,你只能得到10ms的精度。值为10ms可能意味着操作根本不需要时间,或者花费了9.9ms,或者花费了19.9ms,这取决于您的运气。

在你的系统上,要么是一些应用程序改变了计时器的频率(Chrome和其他大量动画的应用程序经常滥用),要么是你运行的是Windows 8+,它变成了一个没有计时器的系统。在任何情况下,您的计时器精度为1ms -这就是您在日志中看到的。

如果你想要更高的精度/准确性,你需要使用StopwatchDateTime不是为你使用它的目的而设计的,尽管它通常工作得很好,只要你不太依赖它(DST/闰秒是如此有趣:))。Stopwatch

最后,确保字典的键按您假设的方式工作。你确定这些DbCommand具有你所要求的那种身份吗?这并不像合同要求DbCommand具有引用身份,或者EntityFramework不重用DbCommand实例。