65.9K
CodeProject 正在变化。 阅读更多。
Home

如何将 LINQ 查询性能提高 5 倍?

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.65/5 (58投票s)

2009年7月16日

CPOL

7分钟阅读

viewsIcon

504151

downloadIcon

3629

提升LINQ查询性能的技巧。

目录

引言和目标

LINQ因其性能问题受到了许多早期采用者的批评。好吧,如果你只是使用DBML代码生成器进行拖放操作,我确信你会一团糟。试着这样做,使用DBML创建一个简单的LINQ to SQL项目,然后查看你的SQL Profiler。我确信你再也不会想碰DBML代码生成器了。

在本文中,我们将首先探讨LINQ查询的执行方式,然后我们将讨论已编译的LINQ查询如何帮助我们将应用程序性能至少提升5倍。我的数据可能上下浮动10%,因为我是根据我的环境情况得出这个数字的。

对LINQ还不熟悉?以下是一些快速入门指南

本文需要一些先决条件,如果您对LINQ不熟悉,我建议您查阅以下链接。

深入探讨LINQ查询的工作原理

在我们深入了解如何提高LINQ查询性能之前,让我们首先尝试理解LINQ查询执行所涉及的各个步骤。所有LINQ查询首先都被转换为SQL语句。这种转换还包括检查LINQ查询语法并将查询转换为SQL。

下面是一个简单的LINQ查询,它从客户表中选择数据。然后,此LINQ查询由LINQ引擎转换为必要的SQL语句。

检查语法并相应地生成SQL查询是一项繁琐的工作。每次我们触发LINQ查询时都会执行此任务。因此,如果我们能缓存LINQ查询计划,我们可以更快地执行。

LINQ提供了一种称为已编译LINQ查询的功能。在已编译LINQ查询中,计划被缓存到一个静态类中。正如我们所知,静态类是全局缓存。因此,LINQ从静态类对象中使用查询计划,而不是从头开始构建和准备查询计划。

图:LINQ查询缓存

总的来说,从构建LINQ查询到执行它们,需要执行四个步骤。通过使用已编译的LINQ查询,这四个步骤减少到两个。

图:查询计划绕过了许多步骤

编写已编译LINQ查询的步骤

首先要做的是导入Data.Linq命名空间。

Import namespace using System.Data.Linq;

编写已编译查询的语法有些晦涩,许多开发人员不喜欢这种语法。因此,让我们将语法分解成小块,然后我们将尝试查看完整的语法是什么样的。要执行已编译函数,我们需要编写一个函数指针。此函数应该是静态的,以便LINQ引擎可以使用存储在静态类对象中的查询计划。下面是我们如何定义函数。它以public static开头,表示此函数是static。然后我们使用Func关键字定义输入参数和输出参数。下面是参数序列的定义方式

  • 第一个参数应该是一个数据上下文。因此我们将其数据类型定义为DataContext
  • 后面是一个或多个输入参数。目前我们只有一个,即客户代码,所以我们已经将第二个参数数据类型定义为string
  • 一旦我们完成了所有输入参数,我们需要定义输出的数据类型。目前我们已经将输出数据类型定义为IQueryable

我们将此委托函数命名为getCustomers

public static Func<DataContext, string, IQueryable<clsCustomerEntity>> getCustomers

我们需要调用静态类CompiledQueryCompiled方法,传入DataContext对象,并必须定义输入参数,然后是LINQ查询。对于下面的代码片段,我们没有指定LINQ查询以尽量减少复杂性。

CompiledQuery.Compile((DataContext db, string strCustCode)=> Your LINQ Query );

将上述两个代码片段合并,完整的代码片段如下所示

public static Func<DataContext, string, IQueryable<clsCustomerEntity>>
getCustomers= CompiledQuery.Compile((DataContext db, string strCustCode)=> Your LINQ Query );

然后我们需要将这个静态函数封装在一个静态类中。我们将上面定义的函数封装在静态类clsCompiledQuery中。

public static class clsCompiledQuery
{
    public static Func<DataContext, string, IQueryable<clsCustomerEntity>>
    getCustomers = CompiledQuery.Compile((DataContext db, string strCustCode)
        => from objCustomer in db.GetTable<clsCustomerEntity>()
        where objCustomer.CustomerCode == strCustCode
        select objCustomer);
}

使用编译查询非常简单;我们只需调用静态函数即可。目前,此函数返回数据类型IEnumerable。我们必须定义一个IEnumerable客户实体,该实体将通过getCustomers委托函数填充。我们可以使用clsCustomerEntity类遍历客户实体。

IQueryable<clsCustomerEntity> objCustomers = 
    clsCompiledQuery.getCustomers(objContext, txtCustomerCode.Text);
foreach (clsCustomerEntity objCustomer in objCustomers)
{
    Response.Write(objCustomer.CustomerName + "<br>");
}

性能比较

出于好奇,我们想做一些比较,看看性能差异有多大。我们使用了一个包含3000条记录的简单客户表,并对客户代码运行了一个简单的查询。我们已将示例源代码附在文章中。下面是一个简单的截图

在这个项目中,我们执行了未经查询编译和经过查询编译的LINQ SQL。我们使用System.Diagnostic.StopWatch类记录了时间。性能记录方式如下:我们启动秒表,运行未经编译的LINQ SQL,然后停止秒表并记录时间。我们以相同的方式记录了经过编译的LINQ查询的性能。

我们创建数据上下文对象并启动秒表。

System.Diagnostics.Stopwatch objStopWatch = new System.Diagnostics.Stopwatch(); 
DataContext objContext = new DataContext(strConnectionString);
objStopWatch.Start();

我们运行未经编译的LINQ查询。执行后,停止秒表并记录时间差。

var MyQuery = from objCustomer in objContext.GetTable<clsCustomerEntity>()
where objCustomer.CustomerCode == txtCustomerCode.Text
select objCustomer;
foreach (clsCustomerEntity objCustomer in MyQuery)
{
    Response.Write(objCustomer.CustomerName + "<br>");
}
objStopWatch.Stop();
Response.Write("The time taken to execute query without compilation is : " + 
objStopWatch.ElapsedMilliseconds.ToString() + " MillionSeconds<br>");
objStopWatch.Reset();

现在我们再次启动秒表,运行经过编译的LINQ查询,并记录所花费的时间。

objStopWatch.Start();
IQueryable<clsCustomerEntity> objCustomers = 
    clsCompiledQuery.getCustomers(objContext, txtCustomerCode.Text);
foreach (clsCustomerEntity objCustomer in objCustomers)
{
    Response.Write(objCustomer.CustomerName + "<br>");
}
objStopWatch.Stop();
Response.Write("The time taken to execute query with compilation is : " + 
   objStopWatch.ElapsedMilliseconds.ToString() + " MillionSeconds");

分析结果

在衡量性能时,我们需要查看第一次执行以及后续执行的时间。至少需要8次记录,以便对任何.NET运行时性能进行平均。我们可以从实验中得出两个重要的结论

  • 我们需要忽略第一次读取,因为可能会有大量的.NET框架对象初始化。这可能导致许多错误的结论,因为第一次运行会产生很多干扰。
  • 后续的读数具有真正的实质性差异。它们之间的平均差异是5倍。换句话说,使用未编译执行的LINQ查询比已编译的LINQ查询慢5毫秒。
  未编译 毫秒 查询编译
第一次 4 124
第二次 9 2
第三次 7 2
第四次 7 1
第五次 6 2
第六次 7 2
第七次 6 2
第八次 6 2

下面是一个图形表示,你可以看到编译查询的性能如何优于非编译查询。

用于测试的硬件和软件配置

  • Web应用程序和数据库应用程序在不同的机器上。
  • Web应用程序在Windows XP上运行,使用VS 2008提供的简单个人Web服务器(抱歉各位,但当时没有其他选择)。Web应用程序PC硬件配置为2 GB RAM,P4,80 GB硬盘。
  • 数据库是SQL Server 2005,运行在Windows 2003 Server上,配置为2 GB RAM,P4,80 GB硬盘。

如需进一步阅读,请观看以下面试准备视频和分步视频系列。

© . All rights reserved.