EF性能分析(一):动态SQL性能差.从OrderBy开始分析

1. 问题背景

在我的力推下,部门业务开发转向ABP,其中ORM采用的是EntityFrameworkCore.
然而,在数据查询方面,出现了重大的性能问题...

请看代码:

//在一个百万数据量的表中分页获取十条数据居然花了180ms左右,简直不能忍。
var entityList = await query
                    .PageBy(input)
					//这是个字符串:MonthCode desc
                    .OrderBy(input.Sorting)
                    .ToListAsync();

这是很常见的Abp示例项目中的CURD中的常规代码,被大量使用...

2.分析问题

2.1 遇到问题先猜,提高查问题效率

开始我平淡的猜测...

a. 整段代码平淡无奇,【但是OrderBy的出现】直接解决了任意字段排序的问题,简直解放双手,要知道百万数据在前端排序是不可能的。
b. 【问题只能被转移,不能被消灭】 --我的编程思想
c. 所以,问题初步定在Orderby上。

2.2 猜到问题代码,继续猜可能的原因

a. 按下F12查看函数签名:

OrderBy(this IQueryable source, ParsingConfig config, string ordering, params object[] args);

b. 开始感性的猜测,只需要一路F12即可。

1. IOderQueryable继承自IQueryable
2. IQueryable由:Type(类型),Expression(表达式树),Provider(表达式树的解析器)组成
3. Expression的构建需要涉及到元数据反射创建。
4. 反射!!!元数据!!!所以真相可能是:从SQL字符串,动态生成Expression!然后,创建IOrderQueryable,最后由EF的Provider解析出SQL,而不是简单的直接作为Orderby字符串拼接...

2.3 猜个大概了,开始证明它吧!

2.3.1 查看OrderBy源码

我用ILSpy反编译工具

public static IOrderedQueryable OrderBy(this IQueryable source, ParsingConfig config, string ordering, params object[] args)
{
	Check.NotNull<IQueryable>(source, "source");
	Check.NotEmpty(ordering, "ordering");
	ParameterExpression[] parameters = new ParameterExpression[]
	{
		ParameterExpressionHelper.CreateParameterExpression(source.ElementType, string.Empty)
	};
	//果真String转Expression,还没有缓存,是快不起来了...
	IEnumerable<DynamicOrdering> arg_48_0 = new ExpressionParser(parameters, ordering, args, config).ParseOrdering(false);
	Expression expression = source.Expression;
	foreach (DynamicOrdering current in arg_48_0)
	{
		expression = Expression.Call(typeof(Queryable), current.MethodName, new Type[]
		{
			source.ElementType,
			current.Selector.Type
		}, new Expression[]
		{
			expression,
			Expression.Quote(Expression.Lambda(current.Selector, parameters))
		});
	}
	Expression expression2 = DynamicQueryableExtensions.OptimizeExpression(expression);
	return (IOrderedQueryable)source.Provider.CreateQuery(expression2);
}
2.3.2 初步结论:

为什么是初步结论呢,,,因为EF还有个二次缓存机制不是...热启动怎么也这么慢,是不是得查它

所以:OrderBy的时候,是由字符串,反射生成表达式树后,创建Queryable,交给EF做后续处理!所以,性能是快不起来的,这里性能大概就消耗了80ms左右!

2.3.3 开始查EntityFramework的缓存机制

其实这个阶段不用查...因为OrderBy每次都会执行生成Expression的过程,所以性能稳稳有问题,但是我真的好奇...

EFCore执行查询的源码

 public virtual TResult Execute<TResult>(Expression query)
        {
            Check.NotNull(query, nameof(query));

            var queryContext = _queryContextFactory.Create();

            query = ExtractParameters(query, queryContext, _logger);
			//获取缓存的地方
            var compiledQuery
                = _compiledQueryCache
					//这个query就是他的key.
                    .GetOrAddQuery(
                        _compiledQueryCacheKeyGenerator.GenerateCacheKey(query, async: false),
                        () => CompileQueryCore<TResult>(query, _queryModelGenerator, _database, _logger, _contextType));

            return compiledQuery(queryContext);
        }

看一下query的结构

结论:EF的缓存机制是用生成后的表达式树做的Key哦,但是生成表达式树的那段代码的性能损耗是无法避免的了!所以!!!慎用!!!动态SQL!!!

原文地址:https://www.cnblogs.com/Imaigne/p/10573767.html