C# Linq查询中直接调用Text.ToUpper()性能缓慢的原因咨询
这个问题我之前排查过类似的场景,核心原因和LINQ到数据库的查询转换逻辑直接相关,咱们来拆解清楚:
第一种慢查询的问题所在
看这段代码:
recent_items returnedData = (from d in db.recent_items where d.item_number == scannerInput.Text.ToUpper() select d).FirstOrDefault();
这里的scannerInput.Text.ToUpper()是在客户端代码里直接执行的方法调用。EF(或者你用的LINQ to SQL)在解析查询表达式树的时候,没办法把这个C#的ToUpper()方法转换成数据库能识别的SQL函数(比如SQL Server的UPPER())。
这就导致了一个糟糕的结果:EF会把recent_items表的所有数据都拉取到本地内存,然后在客户端逐一对比每一行的item_number和scannerInput.Text.ToUpper()的结果。如果表数据量很大,这个全表加载+客户端过滤的操作自然会慢到离谱,也就是你看到的5-8秒耗时。
第二种快查询的优化原理
再看这段优化后的代码:
string search = scannerInput.Text.ToUpper(); recent_items returnedData = (from d in db.recent_items where d.item_number == search select d).FirstOrDefault();
这里我们提前在客户端把字符串转成大写,得到一个固定的字符串变量。EF解析这个查询时,会把search当作参数传递给数据库,生成的SQL大概是这样的:
SELECT TOP 1 * FROM recent_items WHERE item_number = @p0
如果你的item_number字段上创建了索引,数据库就能直接利用索引快速定位到匹配的行,不需要全表扫描,这就是为什么耗时能降到0.5秒以内。
额外的优化建议
如果你不想提前把值存到变量里,也可以用EF提供的数据库函数来让查询生成高效的SQL,比如:
recent_items returnedData = (from d in db.recent_items where EF.Functions.ToUpper(d.item_number) == scannerInput.Text.ToUpper() select d).FirstOrDefault();
不过要注意:如果用函数包裹了item_number字段,原本的索引可能无法被数据库利用,所以最好还是保证数据库里的item_number存储是统一大小写,或者给字段设置不区分大小写的排序规则,这样直接对比就能得到正确结果,还能最大化利用索引。
内容的提问来源于stack exchange,提问作者Joe Ruder

