异步查询数据库怎么可能需要0个tick?
How can it take 0 ticks to query the database asynchronously?
我正在尝试使用 IDbInterceptor
to time Entity Framework's query executions, as accurately as possible, implementing a variant of Jonathan Allen's answer to a similar question:
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 个滴答。它似乎只在我 运行 它异步时发生,同时有一些查询。另一件需要注意的事情是,当我再次查询相同的少量查询时,并不总是需要 0 个滴答的相同查询。
此外,我目前正在测试的数据库位于本地网络上,而不是本地计算机上 - ping 时间是 0-1 毫秒 - 所以即使数据被缓存,我也可以'看不出它怎么可能需要 0 个刻度。
需要注意的一点是,大多数查询花费的时间令人怀疑地接近 1、2 和 3 毫秒(例如 0.997 毫秒到 1.003 毫秒)。对我来说,这听起来像是 OS 旋转线程 cpu-time and/or 1ms 休眠。我不介意它发生,但我只是想知道为什么,这样我就可以解释结果中的不准确之处。
可能与ConcurrentDictionary
有关。但是,当我现在正在测试时,我目前只调用一次异步 (WCF) 方法,等待每个异步数据库调用,所以据我所知,它甚至不应该一次触发更多调用。这就是所谓的例子:
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 和其他大量使用动画的应用程序经常滥用),或者您 运行 onw Windows 8+ ,它转移到无滴答计时器系统。无论如何,您的计时器精度为 1 毫秒 - 这就是您在日志中看到的。
如果您想要更高的 precision/accuracy,则需要使用 Stopwatch
。 DateTime
无论如何都不是为您的用途而设计的,尽管只要您不过分依赖它,它通常就可以很好地工作(DST/leap 秒非常有趣 :) ). Stopwatch
是。
最后,请确保您的词典键按照您假设的方式工作。您确定那些 DbCommand
具有您需要的那种身份吗?这不像合同要求 DbCommand
具有参考标识,或者 EntityFramework 不重用 DbCommand
个实例。
我正在尝试使用 IDbInterceptor
to time Entity Framework's query executions, as accurately as possible, implementing a variant of Jonathan Allen's answer to a similar question:
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 个滴答。它似乎只在我 运行 它异步时发生,同时有一些查询。另一件需要注意的事情是,当我再次查询相同的少量查询时,并不总是需要 0 个滴答的相同查询。
此外,我目前正在测试的数据库位于本地网络上,而不是本地计算机上 - ping 时间是 0-1 毫秒 - 所以即使数据被缓存,我也可以'看不出它怎么可能需要 0 个刻度。
需要注意的一点是,大多数查询花费的时间令人怀疑地接近 1、2 和 3 毫秒(例如 0.997 毫秒到 1.003 毫秒)。对我来说,这听起来像是 OS 旋转线程 cpu-time and/or 1ms 休眠。我不介意它发生,但我只是想知道为什么,这样我就可以解释结果中的不准确之处。
可能与ConcurrentDictionary
有关。但是,当我现在正在测试时,我目前只调用一次异步 (WCF) 方法,等待每个异步数据库调用,所以据我所知,它甚至不应该一次触发更多调用。这就是所谓的例子:
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 和其他大量使用动画的应用程序经常滥用),或者您 运行 onw Windows 8+ ,它转移到无滴答计时器系统。无论如何,您的计时器精度为 1 毫秒 - 这就是您在日志中看到的。
如果您想要更高的 precision/accuracy,则需要使用 Stopwatch
。 DateTime
无论如何都不是为您的用途而设计的,尽管只要您不过分依赖它,它通常就可以很好地工作(DST/leap 秒非常有趣 :) ). Stopwatch
是。
最后,请确保您的词典键按照您假设的方式工作。您确定那些 DbCommand
具有您需要的那种身份吗?这不像合同要求 DbCommand
具有参考标识,或者 EntityFramework 不重用 DbCommand
个实例。