[转载]存储过程 vs ORM 性能大比拼

[转载]存储过程 vs ORM 性能大比拼 – fish-li – 博客园.

其实早就准备好这个测试项目了,一直还忘记了写出来。今天又完善了一下测试用例,打算把它贴出来。

我是一个比较喜欢使用存储过程的人,自然经常会写很多存储过程。 但现在连MS也在关注ORM了,而且还做了二个了,所以也不得不了解一下了。 同时也为了检验一下自己写的通用数据访问层的性能,所以就写了个性能测试程序来将存储过程与ORM的性能做个比较, 当然了,也把我的通用数据访问层与原生的Ado.net的性能做了一番比较。

点击此处下载完整源代码

要测试比较的数据访问方式

这个测试程序提供了5种数据访问的方式来做进行测试。

1. FishWebLib-自动加载实体。(使用我的通用数据访问层,加载实体时,使用反射)

2. FishWebLib-手工加载实体。(使用我的通用数据访问层,加载实体时,无反射)

3. Ado.net直接调用存储过程

4. LINQ-TO-SQL

5. Entity Framework

说明一下,
第一种方式,我想有些人一看到反射就会想到性能差。这里我只想补充一句,反射的性能差不差,要看你如何去写,性能差不差要看结果。
另外,前三种方式全都采用调用存储过程的方式来进行测试。
后二者嘛,都属于ORM工具,就直接使用它们所提供的动态生成SQL语句的方式来进行测试。

停一下,为什么没有采用直接发出SQL语句的方式来进行测试??
好吧,我来解释一下:的确可能有些人喜欢在C#中使用SQL语句的方式来操作数据库,这里就又可以为了二种方式了:
1. 采用SQL语句,并且采用参数的方式。我种做法本身是没有问题的,只是我不喜欢在C#代码中混着一堆SQL语句,看起来代码不清爽。 而且SQL语句放在C#中也没有语法着色的提示,写错了也不知道,并且不方便在数据库的一些工具中测试运行, 如果后期想优化语句,还得重新编译程序,再部署,太麻烦了。所以我不喜欢。
2. 拼接SQL语句。这种方式嘛,我真的不想评价它。

以上二种采用SQL语句的方式,不管如何,肯定快不过存储过程!,所以这里就不提供这种方式了。

比较哪些操作?

一般说来,数据库类的应用程序,操作数据库也就那么几种方式,增,删,改,查询分页列表,获取单个对象。 所以本次测试也就测试这5个方面。我想应该是很有代表性的。

再来看看实体类的定义吧(表结构与实体类对应,就不贴图了)

public class Customer
{
    public int CustomerID { get; set; }
    public string CustomerName { get; set; }
    public string ContactName { get; set; }
    public string Address { get; set; }
    public string PostalCode { get; set; }
    public string Tel { get; set; }
}

分页的搜索参数

class QueryParam
{
    public string SearchWord = "上";
    public int PageIndex = 1;
    public int PageSize = 20;
    public int TotalRecords;
}

至于C#的测试代码嘛,我就挑2种出来,其它的完整代码及存储过程,您可以下载压缩包,打开项目来看。

1. FishWebLib-自动加载实体 的测试代码


// 为了避免打开关闭连接的额外的时间,这里的所有操作全部放在一个连接中。

using( FishDbContext db = new FishDbContext(false) ) {
    Customer customer = new Customer();
    customer.CustomerName = Guid.NewGuid().ToString();
    customer.ContactName = "ccccccccccc";
    customer.PostalCode = "123456";
    customer.Address = "aaaaaaaaaaaaaaaaaaaaaaaa";
    customer.Tel = "12345678";

    // Add
    FishBLLHelper.CallSpExecuteNonQuery(db, "InsertCustomer", customer);

    customer.ContactName = Guid.NewGuid().ToString();
    customer.PostalCode = "430076";
    customer.Address = "湖北武汉";
    customer.Tel = "87654321";

    // Update
    FishBLLHelper.CallSpExecuteNonQuery(db, "UpdateCustomer", customer);

    // Get One
    Customer ccc = FishBLLHelper.CallSpGetDataItem<Customer>(db, "GetCustomerById", null, customer.CustomerID);

    if( ccc == null || ccc.Tel != customer.Tel )    // 检验查询结果
        throw new Exception("call GetCustomerById failed.");

    // Delete
    FishBLLHelper.CallSpExecuteNonQuery(db, "DeleteCustomer", null, customer.CustomerID);

    // 分页查询:要求不仅取到列表,还要知道符合条件的记录数。
    QueryParam param = new QueryParam();
    List<Customer> list = FishBLLHelper.CallSpGetDataItemList<Customer>(db, "GetCustomerList", param);
    int recCount = param.TotalRecords;
}

4. LINQ-TO-SQL 的测试代码

using( MyNorthwindDataContext db = new MyNorthwindDataContext() ) {
    db.Connection.Open();

    Customer customer = new Customer();
    customer.CustomerName = Guid.NewGuid().ToString();
    customer.ContactName = "ccccccccccc";
    customer.PostalCode = "123456";
    customer.Address = "aaaaaaaaaaaaaaaaaaaaaaaa";
    customer.Tel = "12345678";

    // Add
    db.Customer.InsertOnSubmit(customer);
    db.SubmitChanges();

    customer.ContactName = Guid.NewGuid().ToString();
    customer.PostalCode = "430076";
    customer.Address = "湖北武汉";
    customer.Tel = "87654321";

    // Update
    db.SubmitChanges();

    // Get One
    Customer ccc = db.Customer.Where(c => c.CustomerID == customer.CustomerID).FirstOrDefault();

    if( ccc == null || ccc.Tel != customer.Tel )    // 检验查询结果
        throw new Exception("call Get One failed.");

    // Delete
    db.Customer.DeleteOnSubmit(ccc);
    db.SubmitChanges();

    // 分页查询:要求不仅取到列表,还要知道符合条件的记录数。
    MyDataItem.QueryParam parm = new MyDataItem.QueryParam();
    var query = from cust in db.Customer
                where cust.CustomerName.Contains(parm.SearchWord)
                select cust;

    int recCount = query.Count();
    List<Customer> list = query.Skip(parm.PageIndex * parm.PageSize).Take(parm.PageSize).ToList();
}

测试环境及结果

我的测试机器配置(ThinkPad SL510):Core2 T6670 2.2GHz, 4G内存
操作系统:Wiondow server 2003 SP2
数据库:Sql Server 2005 Express SP4

由于以前听说MSDE有并发限制,我想现在的2005 Express版应该也会有限制,所以只开了2个线程。而且由于不想在测试时等太久,所以测试次数也就选了1000次, 有点少,如果您有兴趣自己来试试,可以下载压缩包,里面有完整的代码。

一般说来,应用程序在第一次连接数据库时会慢一些,由于听说.net中有所谓的连接池的概念, 所以本测试程序在运行测试前,会根据线程的数量创建相应数量的连接,然后关闭它们,就算是给连接池做好了准备。
而且每次的测试结果只取程序启动后第一次的运行结果。

贴测试结果:

让人意外的是:Entity Framework比IINQ-TO-SQL慢了不少。比“Ado.net直接调用存储过程”就差更远了。

存储过程还是没让我失望,仍然是我最信赖的技术。所以呢,如果您想让程序有更好的性能,还是建议使用存储过程, 我想这也是为什么即使那些ORM工具一方面可以动态生成SQL,却仍然要支持调用存储过程的原因,在性能面前没有任何技术能取代存储过程!

当然了,如果某些小的项目,或者不经常执行的逻辑中,或者准确的说:对性能要求不高的地方,使用ORM也行吧。
我想有些人在性能面前又会扯到缓存,的确缓存可以在某些时候改善性能,因此,可能会说:我有缓存,存储过程不需要也很好啊。 但是,我也想说:我用存储过程,再加上缓存,会不会更快呢?

说到存储过程,让我想到一件难忘记的事: 今年早些时候去汉庭面试时,最后遇到一位“牛人”,当时我刚提到存储过程,没想到就听到对方的一堆对于存储过程的批评, 最后还说出一句很雷人的话:“存储过程只适合做点小项目!”, 哎,我当时真想说:“你去看看Sharepoint,TFS,看看人家微软用了多少存储过程!”

我知道,在这个世上的确存在一些存储过程的反对者。但是,我上家公司所有做开发的同事,全是支持存储过程的,当然了,他们也都会写存储过程, 问他们存储过程好不好,他们可以说出一大堆存储过程的优点。那些反对者所谓的缺点,在他们看来,并不认为是缺点,这里也就不想列举了。

我呢,只想说二句话:
1. 不会存储过程的人,建议去学学存储过程,学习肯定是有代价的,尤其是不同的数据库的存储过程语法不一样。
2. 会存储过程的人,可以去看看ORM,不用太投入,了解一下它们就好。

在这个测试程序项目中,还有一个问题未能解决,Entity Framework支持POCO的方式来使用(放在目录EFPOCO),我也写了相应的代码, 但项目中同时存在使用Entity Framework设计器生成的代码(放在目录EF), 当运行Entity Framework POCO的代码时,会出现异常“找不到“WinFormApp.EF.Customer”的概念模型类型”。 这二种方式单独运行时(在项目中排除另一个目录),是没有任何问题的,总之就是不能混用这二种方式。
如果谁知道原因,请帮我修改一下。 只要把目录EFPOCO加入到项目中,然后取消注释TestFactory.cs中的一些代码就可以重现异常了。在此先谢谢了。

点击此处下载完整源代码

赞(0) 打赏
分享到: 更多 (0)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏