posts - 165,  comments - 880,  trackbacks - 42
       存储过程在数据库本地编译本地运行显然是提高数据库访问效率的首选,但实际中有不少朋友误解了只要是存储过程就是高性能的表现,认为只要是存储过程实现的逻辑操作就肯定比客户端操作数据库效率高;但他们却忘记重要的一点就是效率很大程度是依赖于具体实现的代码。接下来做个简单的测试证明这一点。

       数据分页功能是经常讨论的话题,在很多人眼中在dotNet的Client端进行数据分页是非常低效,根本不可能和存储过程匹敌;即使很糟糕的存储过程代码也会比dotNet的Client要快。为了证实这一点于是编写了一个简单的dotNet分页代码和传统的MSSQL用临表进行分页的存储进行了比较。

测试描述:

       对MSSQL NorthWind数据的Orders表进行分页操作,分页的总记录数830条。对相关分页代码进行10次调用,每次调用分别执行1页到5页的数据集获取。

dotNet分页代码:

const string getindex = "select orderid from orders";

        const string getrecord = "select * from orders where orderid in ({0})";

        static System.Data.DataTable GetDataSQL(int page)

        {

            System.Text.StringBuilder index = new StringBuilder();

            SqlDataAdapter da;

            System.Data.DataSet ds = new System.Data.DataSet();

            SqlCommand cmd = new SqlCommand();

            int start, end;

            start = 10 * page;

            end = start + 10;

            int cur = 0;

            using (SqlConnection conn = new SqlConnection("data source=.;initial catalog=northwind;user id=sa;pwd=;"))

            {

                cmd.Connection = conn;

                cmd.CommandText = getindex;

                conn.Open();

                using (SqlDataReader reader = cmd.ExecuteReader())

                {

                    while (reader.Read())

                    {

 

                        if (cur >= start && cur < end)

                        {

                            if (index.Length > 0)

                            {

                                index.Append(",");

                            }

                            index.Append("'" + reader[0].ToString() + "'");

 

                        }

                        if (cur > end)

                        {

                            cmd.Cancel();

                            break;

 

                        }

                        cur++;

                    }

 

                }

                cmd.CommandText = string.Format(getrecord, index.ToString());

                da = new SqlDataAdapter(cmd);

                da.Fill(ds,  "table1");

                return ds.Tables[0];

 

            }

 

        }

 

存储过程分页代码:

CREATE    PROC  testList

(

 @PageIndex int,

 @PageSize int

       

)

as

DECLARE @PageLowerBound int

DECLARE @PageUpperBound int

SET @PageLowerBound = @PageSize * @PageIndex

SET @PageUpperBound = @PageLowerBound + @PageSize + 1

 

Create Table #IDs

(

 TempID int IDENTITY,

 orderid  int not null

)

Insert  into #IDs(orderid)  select orderid from orders 

SELECT  orders.*

FROM   orders 

     INNER JOIN #IDS  ON (orders .[orderid] = #IDS.orderid)

WHERE  #IDS.TempID > @PageLowerBound

 AND #IDS.TempID < @PageUpperBound

GO

        static System.Data.DataTable GetDataSP(int page)

        {

 

            SqlDataAdapter da;

            System.Data.DataSet ds = new System.Data.DataSet();

            SqlCommand cmd = new SqlCommand();

            using (SqlConnection conn = new SqlConnection("data source=.;initial catalog=northwind;user id=sa;pwd=;"))

            {

                cmd.Connection = conn;

                cmd.CommandText = "testList";

                cmd.CommandType = System.Data.CommandType.StoredProcedure;

                cmd.Parameters.Add("@PageIndex", page);

                cmd.Parameters.Add("@PageSize", 10);

                conn.Open();

 

                da = new SqlDataAdapter(cmd);

                da.Fill(ds, "table1");

                return ds.Tables[0];

 

            }

        }

 

测试结果:

存储过程分页:74毫秒

dotNet分页:64毫秒

存储过程分页:21毫秒

dotNet分页:10毫秒

存储过程分页:1023毫秒

dotNet分页:11毫秒

存储过程分页:20毫秒

dotNet分页:11毫秒

存储过程分页:22毫秒

dotNet分页:12毫秒

存储过程分页:1031毫秒

dotNet分页:10毫秒

存储过程分页:20毫秒

dotNet分页:10毫秒

存储过程分页:21毫秒

dotNet分页:10毫秒

存储过程分页:20毫秒

dotNet分页:10毫秒

存储过程分页:21毫秒

dotNet分页:10毫秒

是什么情况导致效率相差这么远,而效率低下的却是存储过程而并不是dotNet代码。相信大家看一下存储过程的处理应该知道原因所在.

下载测试代码


posted on 2007-06-13 21:51 henry 阅读(2742) 评论(45)  编辑 收藏

FeedBack:
2007-06-13 22:10 | YAO.NET℡      
你应该用100万条数据测一下.

  回复  引用  查看    
2007-06-13 22:11 | zzz [未注册用户]
分页不一定用临时表啊
  回复  引用    
2007-06-13 22:11 | 211 [未注册用户]
830条???
就下此结论???????????
  回复  引用    
2007-06-13 22:49 | <span style="color:red;">Anders Li      
是啊,测试数据量太小了吧?

另外,也简单介绍下“存储过程的处理”啊。
  回复  引用  查看    
2007-06-13 22:50 | winjer [未注册用户]
830条???
就下此结论???????????
____________________________________________
同意!太片面了!
不上100万,都不叫数据。
  回复  引用    
2007-06-13 23:04 | xhp      
看到此篇文章,我立马下载了楼主的代码,找了个10000左右数据进行测试,测试结果吓了我一跳,还真如楼主所言!
以下是对比结果:
dotNet分页:4860毫秒 存储过程分页:13435毫秒
dotNet分页:43毫秒 存储过程分页:246毫秒
dotNet分页:47毫秒 存储过程分页:274毫秒
dotNet分页:40毫秒 存储过程分页:248毫秒
dotNet分页:40毫秒 存储过程分页:245毫秒
dotNet分页:42毫秒 存储过程分页:248毫秒
dotNet分页:39毫秒 存储过程分页:248毫秒
dotNet分页:40毫秒 存储过程分页:420毫秒
dotNet分页:44毫秒 存储过程分页:288毫秒
dotNet分页:40毫秒 存储过程分页:332毫秒

不过这个测试数据是建立在分页存储过程采用临时表处理,可能有一些高手采用别的方法会提高效率。
刚好我的项目也是用这种方法,所以大吃一惊!看来得研究研究其他方案了。
  回复  引用  查看    
2007-06-13 23:06 | xhp      
PS:不管最终结果如何,楼主的专研精神值得一赞!
  回复  引用  查看    
2007-06-13 23:59 | diky [未注册用户]
确实改用大数据试试,
  回复  引用    
2007-06-14 00:14 | 安贝 [未注册用户]
其实软件就是开发者的自身的很多因素决定的,我说我在有100万数据的(25个字段),关联表格,任意查询(除去link ),常规任意查询返回其中一页(20条记录),300毫秒以内,你信吗?而且还有办法提高.
不是技术决定了性能,是写码者决定性能.

  回复  引用    
2007-06-14 01:10 | 若寒      
先谢谢楼主!!
测试代码有误,下载后解压出错,压缩包只有2KB。

  回复  引用  查看    
2007-06-14 05:35 | Jeffer [未注册用户]
存储过程写那么差还是不要用的好。
  回复  引用    
2007-06-14 06:54 | zzbird [未注册用户]
这也叫分页存储过程吗?有这样用的吗?虽然很多人,包括微软这样用,但实际上大部分开发者都不用这种效率低下的办法
  回复  引用    
2007-06-14 08:29 | kiler      
@YAO.NET℡

不要老是动不动就是百万数据,实际上我们开发的系统大部分的表数据量都是在1w以下的,百万数据表只是少数几个而已,lz其实想说的是不要滥用存储过程,只有当操作数据大到一定程度的时侯,使用存储过程才是合理。小数据量的时候使用存储过程效率并不高。

我以前经常可以看到有的人连操作几十条数据的表都要用存储过程。

  回复  引用  查看    
#14楼 [楼主]
2007-06-14 08:32 | henry      
看来很多人以为是记录少导致....
记录越多这样的方法存储过程只会越慢...
对于一些认为数据库提供ROWNUMBER和ROWNUM很可靠的朋友也可以测一下。
其实写就东西的目的只是为了证明代码本身决定效率。
  回复  引用  查看    
2007-06-14 08:37 | sunruping [未注册用户]
没那水平就不要比较存储过程和代码了,老老实实的写代码吧.
  回复  引用    
#16楼 [楼主]
2007-06-14 08:40 | henry      
@安贝
其实只要表有主索引,电脑配置较为好一点,直接用dotnet也能达到这样的效果。不过我们关注的不应该是表有多少数据,而是考虑当前需求操作有多少数据。
  回复  引用  查看    
2007-06-14 08:42 | 太阳神      
楼主你的分页过程的方式好像不对的吧,这样写,又是临时表,速度当然很慢了啊!
  回复  引用  查看    
2007-06-14 08:46 | t-mac [未注册用户]
楼主可测试过分页的最后一页
  回复  引用    
#19楼 [楼主]
2007-06-14 08:54 | henry      
看来很多人一看测试结果就怒了...
根本没有看到写这个东西的用意真没意思.
如果谁想真的拼个你死我活就自己开个新页吧.....
  回复  引用  查看    
2007-06-14 09:01 | kk [未注册用户]
无语!!
  回复  引用    
2007-06-14 09:08 | rehearts [未注册用户]
应该研究一下,为什么会出现这种情况,性能损失在了那部分,存储过程在服务器段预编译 绝对是要比同样的语句操作放在客户端提交块,至少不需要传递太多的sql数据。
  回复  引用    
2007-06-14 09:39 | novel_zhou [未注册用户]
搂主的分页存储过程不止的借鉴,在处理几十万记录的列表时,如果用.net自带的分页功能是会让人无法忍受的。分页的性能高低主要取决于怎么让取得一页的数据时查询打开的记录更少些。建议在简单关键字段排序时候用max或者min方法,而复杂多字段排序时用not in方法。在实际项目中分页读取30万记录的表,一般在2-3秒左右。
  回复  引用    
2007-06-14 10:40 | Edward [未注册用户]
楼主的测试方法不能说明什么问题,首先是存储过程中的分页方法不正确,现在已经没有几个人在使用临时表做分页查询了;其次数据量太小了,应该是在建立索引的情况下使用10万以上的数据量才可以的。
  回复  引用    
2007-06-14 10:41 | Edward [未注册用户]
实际上使用存储过程最大的问题并不是效率,而是将业务逻辑包装了进去,使得项目在维护的过程中变得更加困难而已。
  回复  引用    
#25楼 [楼主]
2007-06-14 10:45 | henry      
@Edward
....我现在已经无语了,文章根本不是比较存储过程快还是client代码快。
那个测试结果只是证明效率和所编写的代码相关..
  回复  引用  查看    
2007-06-14 10:59 | 盛天 [未注册用户]
楼主机了内存是多大的,一次性读取1W记录?
对你测试数据表示怀疑!
另外你写的那个存储过程真不想说再多,简直就是烂屎
  回复  引用    
2007-06-14 11:22 | RicCC      
1. 例子中connection都没有关闭,存储过程调用最好使用dbo.procName
2. 你在两种测试方式里面使用的分页方法不一样,没有可比较性,这两种分页方式都可以用存储过程实现,也都可以用C#代码实现,所以文章取名滥用存储过程的思考不准确
  回复  引用  查看    
#28楼 [楼主]
2007-06-14 11:32 | henry      
@RicCC
1.connection是绝对有关闭
2这个例子目的不是用来比较存储过程分页快还是client分页快.
而是说明即使用存储过程没有良好的代码编写也是无济于事。

  回复  引用  查看    
2007-06-14 11:34 | RicCC      
sorry没有注意到你用的using
  回复  引用  查看    
2007-06-14 12:01 | Axel [未注册用户]
这个测试主要存在两个问题:
(1)dotnet的分页法是把id 全部读到内存中,然后找出分页匹配的数据,这样会使得数据越大,内存的占用就越大。
(2)储存过程的写法是根据dotnet写法的原理转化的,也就是通过自动增值的序号把全部数据读到临时表中,然后得到分页的数据。这种方法的错误在于,每次不需要把全部的数据读到临时表,只需把当前页止的总数目读到临时表,这样一来性能的变化会差异很多。

这个实验确实不能证明什么,如果在代码都正确的情况,存储过程会表现得优异一些。但楼主要清楚的是dotnet代码原理并不代表存储过程的代码原理。
  回复  引用    
2007-06-14 12:05 | web报表      
看到上百行的存储过程就头痛.
  回复  引用  查看    
2007-06-14 12:06 | quickcn [未注册用户]
sql server 为什么不内建一种快速分页的机制呢?分页需求太普遍了。

MYSQL就不错,有limit
  回复  引用    
2007-06-14 12:15 | Axel [未注册用户]
再谈存诸过程分页:
(1)如果需要临时表方案,也应尽可能用表变量,而不是临时表,这样性能会更好。
(2)分页完全可以不需要临时表,从下面的select语句中可以转化为分页方案,性能在超过1000条以上会快很多。
从publish 表中取出第 n 条到第 m 条的记录:
SELECT TOP m-n+1 *
FROM publish
WHERE (id NOT IN
    (SELECT TOP n-1 id
     FROM publish))

id 为publish 表的关键字
  回复  引用    
#34楼 [楼主]
2007-06-14 12:29 | henry      
@Axel
写表进行分页的方法根本不可取,主要数据库的读操作远高到写操作。即使是写入需要的记录当需要后面记录时就无可避免写入大量记录。
事实上要得到更高效率的数据访问存储过程首选,但有很多人误以为只要是存储过程效率就高,往往不会注重存储过程应用场景和代码质量。本文的出发点就是这样而并不是比较存储过程和client操作高低,在同样质量的代码下client操作数据是不可能比得过存储过程的。
  回复  引用  查看    
2007-06-14 13:35 | Axel [未注册用户]
@henry
是的,引发这个讨论的原因我想因为题目的本身,这样会引起少许的误解。
  回复  引用    
2007-06-14 14:19 | kiler      
这帖子已经变成讨论怎么提高存储过程的性能了,很多回复的人看来还是没有理解lz写这篇文章的用意啊。
  回复  引用  查看    
2007-06-14 15:45 | test [未注册用户]
你的存储过程的效率太低了,没法比
  回复  引用    
2007-06-14 21:19 | 小鬼 [未注册用户]
哈,这不是存储过程的错.只是用的人的错而已.
  回复  引用    
2007-06-14 22:28 | tong3q [未注册用户]
把条件#IDS.TempID > @PageLowerBound
AND #IDS.TempID < @PageUpperBound 改写成 #IDS.TempID >= @PageLowerBound
AND #IDS.TempID <= @PageUpperBound 再对比一下。这样存储过程的速度会有提高。


  回复  引用    
2007-06-15 00:11 | laifangsong      

首先是数据少,不具有代表性。数据量较大时(比如50万条),不敢想象点击到最后一页时,datareader里做了50w次循环要花多少时间。

其次,用创建临时表方法很不可取,虽然创建临时表对 各种有查询条件的分页存储过程支持的比较好(比如 文章列表分页,要实现文章置顶有些比较高效的分页存储过程不能用),但创建临时表消耗了很多内存和时间,完全可以用

2个 top 的写法代替,相信效果也比测试中的数据要好的多。
  回复  引用  查看    
2007-06-15 02:31 | 木野狐      
sql server 有个内建的所谓未公开分页存储过程可以拿来用一用。具体我忘记了,google 一下。
临时表确实效率低。
  回复  引用  查看    
#42楼 [楼主]
2007-06-15 08:57 | henry      
@laifangsong
真没意思你还不知道我的用意何在,如果非要我告诉你datareader在良好网络资源情况下的效率我就说一下在p4 1.8 512m 的电脑通过索引分页对100W记录拿任何一页都是秒杀(20/页 MSSQL数据库,如果页记录数据大难说).如果你做不到就检查自己的代码不要把问题推到datareader身上.用存储过程实现这样方法效率更高.
  回复  引用  查看    
2007-06-15 11:10 | silverlightfans      
楼主的sql server知识实在不敢恭维,sql server 在使用临时表的存储过程当中是不可以被预编译的。
还有这样的测试对比欠妥。至少要测试1000行数据和1000000行数据对比才能出综合结果。
  回复  引用  查看    
2007-06-15 11:20 | laifangsong      
@henry
先向博主道个歉,我确实没有测试过 50w在datareader中循环的速度,只是感觉慢。

你说的 “p4 1.8 512m 的电脑通过索引分页对100W记录拿任何一页都是秒杀”,在单机上测试出现这个结果应该也没什么问题。

但是分页程序放在网络上n人同时访问时,n次的100w访问,是要消耗很多内存的。

存储过程可以做到“按需去取”,datareader却不能,差别就在这里。
  回复  引用  查看    
#45楼 [楼主]
2007-06-15 11:42 | henry      
@laifangsong
其实如果真的去遍历100W记录所有数据,不要说程序就算存储过程在并发下也支撑不起道理是一样的。在存储中通过主索引进行分页获取,datareader同样样遍历主索引分页然后根据主索引获取记录,只是操作方式改变了一下.
任何事情都不想当然的...
事实上datareader作为客户端游标的确是存在网络访问上的损耗.
到此为止..本文的目的不是要比谁快谁慢.存储过程快是不用质疑的,但最终还是依赖于具体代码实现。
  回复  引用  查看    
<2007年6月>
272829303112
3456789
10111213141516
17181920212223
24252627282930
1234567

寻求伯乐,限广州地区有意联系


与我联系

搜索

 

常用链接

留言簿(21)

我参加的小组

我的标签

随笔分类

最新评论