onceProject 2009-09-14
使用LINQ查询时数据操作的性能是如何的呢?我们在进行LINQ查询的使用时会有什么效果呢?那么这里就向你提供一点对于LINQ查询时的数据操作的性能相关介绍。
LINQ查询的一点体会
当我们会熟练的使用以上的查询方法对数据库里的内容做各种各样的查询以后,就应当要了解这些数据库操作的机制,及时调整各种数据操作语句,以较高的效率运行。那么,下面我们来看看LINQ的数据库操作,看看她都做了些什么工作。
与NHibernate来比较,LINQ在O/R Mapping的性能与可控性上确实优于NHibernate,首先,Linq默认的数据映射采用的是Attribute来实现,这是.NET特有的语法,在编译时就已经决定了数据对象的各种属性,而NHibernate等大多数O/RMapping工具仍然采用XML映射文件来描述数据对象的属性,从外部文件上读取数据对象的属性,显然运行时效率要有所损失。其次,在获得数据的方式上也有所差别,LINQ中强大的SQL分析机制,可以分析出各种数据操作的SQL语句,并且进行优化,其效率的提升也是显而易见的。
当然,作为一个O/R Mapping的工具来说,其效率一定达不到直接使用SQL语句访问数据库的效率,也就是我们通常所说的SqlDataReader/SqlDataAdapter访问数据库,但是,Linq的表现却给了我们非常大的惊喜,我做了一个测试,使用SqlDataReader和LINQ做相同的大批量数据查询时,落后竟然不到10%,而NHibernate的查询效率,却低了很多,几乎慢了1倍。对于如此强大的数据映射功能,这样的效率是我们可以接受的。但是很可惜的一点是,LINQ目前只能支持对SQLServer的支持(但可以支持XML、Entity等)。
在使用LINQ查询进行数据查询上,我们通过对LINQ生成的SQL语句进行分析,便可以优化查询,这是非常方便的,但是,针对数据更新的效率问题,我们不得不谈谈LINQ的数据更新机制,一般情况下,数据更新我们会这么做:
var query = from emp in dbdata.Employees where emp.DepId=="1001" select emp; Employee employee = query.First(); employee.EmployeeName = "李四"; dbdata.SubmitChanges();
对于以上这段代码,我们可以看出,其功能是从Employee表中取出部门代码为1001的所有员工,然后我们取出第一条数据(这里为了简便,我们仅仅取出第一条,其实可以用Where取出满足条件的记录),然后把名字修改成“李四”,再更新到数据库中。这段代码,LINQ都干了些什么呢?通过查询从数据库中取出若干条记录,放在内存中,并且都标记为new(未改变)状态,当修改了员工姓名的时候,被修改的对象被标记为Dirty(已改变),在SubmitChanges的时候,再为内存中对象状态为Dirty的记录自动生成SQL语句并执行,也就是说,我们要完成一次数据的更新,至少要完成一次查询和一次更新。
LINQ查询的一点分析:
由于采用了延时加载(Layze Load)的技术,在以上语句中实际从数据库中取出的记录只有1条,更新的时候也只更新这一条,因此效率仍然是非常高的,我在测试的过程中发现,从250000条数据中随机抽取一条进行更新,实际的效率和从10条数据中随机抽取一条进行更新几乎没有差别,因为比较更新状态是在内存中进行,因此效率是比较高的。下面我们再看看实际的更新生成了什么样的SQL语句:
UPDATE [dbo].[Employee] SET [EmployeeName] = @p4 WHERE ([EmployeeId] = @p0) AND ([DepId] = @p1) AND ([EmployeeName] = @p2) AND ([EmployeeSalary] = @p3)
原来,我们只修改了EmployeeName的字段,生成的SQL语句却也仅仅是更新了Employee字段。那么,我们再看看后面的条件,为什么会包含除了主键以外的其他条件呢?原来,这也是LINQ自动生成SQL语句的严谨所在,这是为了防止并发情况下,多个事务针对同一条记录更新时发生错误,假如A事务更新了该记录,则B事务更新会失败。我们不禁要问,假如要更新主键字段怎么办?会不会错误的更新到多条记录呢?答案是肯定的,肯定会错误的更新到其他记录,因此,LINQ中规定了主键字段是不允许更新的,如果确实要更新,那么就删除掉该记录,重新插入新纪录。这么严谨的SQL语句,会给我们带来一些麻烦,我们来看下面一个应用场景:
如果我们在表中设有一个字段用于计数器,使用SQL语句是这样的:
Update CountTable set CountColumn= CountColumn+1 where CountId=@countId
但使用LINQ生成的Sql语句却是:
UPDATE [dbo].[CountTable] SET [CountColumn] = @p2 WHERE ([CountId] = @p0) AND ([CountColumn] = @p1)
@p2这个参数是计算好后传入的,@p1这个参数是CountColumn原来的值。也就是说,CountColumn+1这个值不是由数据库运算出来的,这样一来,当并发数很高的时候,我们往往会更新失败。我做了个测试,使用多线程模拟多用户的情况下进行计数统计,数据库中统计的值比使用静态变量保存的值要小,这也就是说数据库更新是存在失败的情况。另外,这样每次的更新,需要完成的操作有查找和更新两个步骤,因此对于效率也有比较大的影响。