异步查询数据库怎么能花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
...
}
顺便说一下,我知道我可能应该转换整个应用程序来使用导航属性,但这是我们目前所拥有的,所以请原谅我。
向您的网络管理员致敬-看到如此低延迟的网络是相当罕见的。
DateTime.UtcNow
具有与系统计时器相同的分辨率(这并不奇怪,因为系统计时器更新当前时间:))。默认情况下,在Windows NT上,这是10ms -所以在一个干净的系统上,你只能得到10ms的精度。值为10ms可能意味着操作根本不需要时间,或者花费了9.9ms,或者花费了19.9ms,这取决于您的运气。
在你的系统上,要么是一些应用程序改变了计时器的频率(Chrome和其他大量动画的应用程序经常滥用),要么是你运行的是Windows 8+,它变成了一个没有计时器的系统。在任何情况下,您的计时器精度为1ms -这就是您在日志中看到的。
如果你想要更高的精度/准确性,你需要使用Stopwatch
。DateTime
不是为你使用它的目的而设计的,尽管它通常工作得很好,只要你不太依赖它(DST/闰秒是如此有趣:))。Stopwatch
。
最后,确保字典的键按您假设的方式工作。你确定这些DbCommand
具有你所要求的那种身份吗?这并不像合同要求DbCommand
具有引用身份,或者EntityFramework不重用DbCommand
实例。