我正在使用 EF Core 6 与 SQL Server 数据库。 有时,SELECT 查询的执行时间超过 30 秒,并超时。
如果我执行由 EF Core 生成的完全相同的 SQL(使用完全相同的参数,在相同的数据库上,超时后仅几秒),它需要不到一秒。
在整个期间,DB server 都在用CPU < 30%.
在 SQL Server Management Studio 上运行 SQL 查询,我可以看到执行计划是理想的(即它使用索引等)。
所以我担心可能会有一些锁定阻止数据库返回查询结果。
有没有办法指定 EF Core 例如查询的隔离级别,甚至一些并发/锁定策略?
在我的特定场景中,可以进行脏读或不完全最新的读取,因为我们有适当的程序来在后续轮查询中检索干净的数据。
谢谢
当 SQL Server 可能因为 Parameter Sniffing 而减慢查询速度时,这不是新问题。可以通过将参数转换为常量或在查询末尾添加 OPTION(RECOMPILE) 来解决问题。该答案将
DbCommandInterceptor
添加到 DbContextOptions
并将 OPTION(RECOMPILE)
提示附加到特定查询。
配置 DbContext
builder.UseSqlServer(connectionString)
.UseRecompileExtensions(); // registering interceptor
如何在查询中使用:
var name = "SomeName";
var result = context.SomeItems
.Where(x => x.Name == name)
.WithRecompile() // it marks query as a query which needs RECOMPILE query hint
.ToList();
然后SQL Server将发送以下SQL:
SELECT [s].[Id], [s].[Name]
FROM [SomeItems] AS [s]
WHERE [s].[Name] = @__name_0
OPTION(RECOMPILE)
和扩展的实现:
我已将所有内容放入一个静态类中以简化答案。在 EF Core 6 上测试过,但也适用于较低版本。
public static class RecompileExtensions
{
private const string RecompileTag = "recompile_query_tag";
private const string RecompileComment = "-- " + RecompileTag + "\r\n";
public static DbContextOptionsBuilder UseRecompileExtensions(this DbContextOptionsBuilder builder)
{
return builder.AddInterceptors(RecompileInterceptor.Instance);
}
public static IQueryable<T> WithRecompile<T>(this IQueryable<T> query)
{
return query.TagWith(RecompileTag);
}
private class RecompileInterceptor : DbCommandInterceptor
{
public static RecompileInterceptor Instance = new();
public override ValueTask<InterceptionResult<DbDataReader>> ReaderExecutingAsync(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result,
CancellationToken cancellationToken = new CancellationToken())
{
CorrectCommand(command);
return base.ReaderExecutingAsync(command, eventData, result, cancellationToken);
}
public override InterceptionResult<DbDataReader> ReaderExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result)
{
CorrectCommand(command);
return base.ReaderExecuting(command, eventData, result);
}
private static void CorrectCommand(DbCommand command)
{
var newQuery = command.CommandText.Replace(RecompileComment, "");
// if query was changed, we have to append RECOMPILE option
if (!ReferenceEquals(newQuery, command.CommandText))
{
// remove rest of the comment
if (newQuery.StartsWith("\r\n"))
newQuery = newQuery.Substring(2);
newQuery += "\r\nOPTION(RECOMPILE)";
command.CommandText = newQuery;
}
}
}
}
不建议重新编译,它可能会损害正在运行的应用程序。 对于大规模数据,我个人推荐使用 DBQuery 或 Simple ADO.net