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!!!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM