zoukankan      html  css  js  c++  java
  • 翻动100万级的数据 —— 只需几十毫秒

    感谢大家的支持!!!
    昨天发了一个邀请,邀请大家帮忙测试,效果还可以,下面小结一下:

    通过内部的计数器得知:访问次数是1071(其中有好多是自己点的:)),人数不是太理想,本来是想看看上万人同时访问的情况:)

    系统资源的占用情况

    内存 —— 很理想。SQL占用的内存最大也没有超过65M,一般是在35M左右;asp.net占用的内存最大也没有超过40M,一般是在25M左右。

    CPU:8%左右,由于访问次数不多,也不够集中,所以这个数值也说明不了什么。自己连续点了n次下一页,发现CPU的使用率飘高,达到了50%左右。
    但是对于100万的记录,AMD XP2000+ 的CPU 几十毫秒的放映速度,因该是可以接受的,甚至是很理想的吧。
    毕竟服务器的CPU要比我的快很多吧,而且记录也很难达到100万吧。

    结果还是很满意的,但是美中不足的是,我想看一下海量访问的情况下的效果,
    希望大家再支持一下,多点几下,谢谢了。呵呵

    另外说明一下:前n页可以在60毫秒内完成,n应该是大于500的,小于多少嘛还没有测试。后n页就比较慢了,需要500毫秒左右。

    下面讨论一下翻页的技巧吧。
    我没有用游标、临时表、not in、in 这些方法,并不是说他们的效率不高,而是我还没有测试过。我只用了 top ,查了两次表。
    大家也可提供一些其他的方法,我来测试一下,看看在100万条的情况下的效果。(请不要给在存储过程里面组串的,看着实在是太费劲了)

     讨论的前提是在海量数据的情况下,至少是在10万以上的。如果是很少的数据呢,那怎么翻都可以了。也差不了多少。

    1.设置合理的索引
    首先要做的是设置合理的索引,这个好像经常被忽略,至少很少被谈起。

    注意:主键是索引的一种,而且是最快的一种。如果你都是把主键当作排序字段的话,那么你已经利用了索引。

    不设置合理的索引的话,会导致查询速度非常的慢,甚至会造成超时。

    这方面你可以做一个实验:找一个表,填进去10万条记录,假设有ID 、addedDate等字段,在查询分析器里面执行一下

    select top 10 * from table
    应该立刻就能出现结果。

    然后再执行 select top 10 * from table order by ID(这时ID字段是主键)
    也是立刻就出现了结果。

    然后再执行 select top 10 * from table order by addedDate (这时addedDate字段没有索引)
    你会发现速度很慢。

    现在给addedDate 加一个非聚集索引,然后在执行上面的查询语句,速度也变得很快了。

    可见索引神奇的效果!

    这是翻动百万级记录最基本的设置,具体到我的那个论坛的翻页,我是设置了BoardID、 replyDate两个字段作为联合索引的。
    因为是要在同一个讨论组李翻页,而且是按replyDate排序的。


    2.只返回需要的记录
    对于海量数据,都读出来做缓存,那是不可想象的(记录少的话,也要看利用率,一般都是很浪费的)。
    所以呢如果一页显示20条的话名那就只都读出来20条,这样就很省内存和时间。

    注意:虽然ADO.NET里面有这个方法
    SqlDataAdapter.Fill(DataSet1,startRecord,maxRecords,srcTable);
    但是他还是要先从SQL里面把查询语句的查出来的所有记录都出来,然后在截取指定的记录数。这对于SQL来说是一样的,对于海量数据依然会很慢。

    论坛里的首页用的是select top 20 * from table where boardID = 5 order by replyDate desc
    这样呢就只返回了20条记录,再加上索引的功劳,速度是非常快的。

     3.尽量减少字段的长度
    一个表可以建很多的字段,但是字段的总长度不能超过8060B,也就是说如果你建了一个char(8060)的字段,就不能在建其他的字段了。

    我在第一次的测试中(星期天的),把主题的所有信息都放在了一个表里面,包括了一个nvarchar(3600)的主题内容的字段,复制记录的时候发现非常的慢,
    当达到9万的时候,就已经很慢的,勉强把记录数拷贝到了35万,加了索引,测试了一下,翻页速度还是可以的,前n也都是很快的,后n页就很慢了,
    如果再加上查询那就非常之慢了。

    查看了一下数据文件吓了一跳 —— 他居然占用了1.4G的硬盘空间,怪不得拷贝和查询都慢的要死呢。

    于是修改了一下表结构,把那个nvarchar(3600)的主题内容的字段踢了出去,放在一个单独的表里面。
    再重新拷贝记录就非常的快了,很快就把记录数从16表成了1048577。昨天的测试就是在这个条件下进行的。

    4.技巧
    终于到了翻页算法的地方了,呵呵没有等急吧。
    思路呢就是先找到一个标志,然后呢把大于(或小于)这个标志的前n条记录取出来。
    什么?没看懂。没关系,我举个例子吧。

    假设是按ID倒序的,每一页显示10条记录,有100条记录,记录号正好是1到100(怎么这么巧??为了说明方便嘛)

    那么第一页的记录就是100到91、第二页的记录就是90到81、第三页的记录就是80到71......

    我现在要翻到第三页,那么要找到第21行的记录的ID的值(也就是80),然后把小于等于80的记录用top 10 取出来就行了。

    查询语句

    declare @pageSize int --返回一页的记录数
    declare @CurPage int --页号(第几页)1:第一页;2:第二页;......;-1最后一页。

    declare @Count int
    declare @id int

    set @pageSize=10
    set @CurPage =1

    if @CurPage = -1
    begin
    --最后一页
    set rowcount @pageSize
    select @id=ID from table order by ID
    end

    --定位
    if @CurPage > 0
    begin
    set @Count = @pageSize * (@CurPage -1) + 1
    set rowcount @Count
    select @id=ID from table order by ID desc
    end

    --返回记录
    set rowcount @pageSize
    select * from table where ID <=@id order by ID desc

    set rowcount 0


    其中“定位”用了 select @id=ID from table order by ID desc
    这种方法,感觉上是很省内存的,因为只记录了一个ID,

    然后用 select * from table where ID <=@id order by ID desc
    取得最终需要的记录

    set rowcount @pageSize 相当于 top @pageSize 。


    优点:无论翻到哪一页,内存的占用情况都不变,多人访问内存也不会不变,很多人呢,还没有测试出来:)
    缺点:单表、单排序字段。

     http://community.csdn.net/Expert/TopicView3.asp?id=4182510

    发了这个帖子,回复的人很多,感谢大家的支持。这里有个误会我不得不说明一下,免的误人子弟。

    在帖子里我并不是写了个算法就完事了,而是说了很多翻动海量数据要注意的地方,

    比如建立合理的索引,只返回需要的记录 ,尽量减少字段的长度 等注意到或没有注意到的地方。

    最后说的才是算法,可能是我的表达能力太差了吧,举的例子给大家带来了误会。

    翻页的语句 ( @pageSize * (@CurPage -1) + 1 )

    --定位
    declare @id int
    select top 41 @id=ID from table order by ID desc

    --显示数据
    select top 20 * from table where ID <=@id order by ID desc

    按照ID倒序排列(也就是按照int类型的字段排序)
    一页显示20条记录,这是显示第三页的语句
    @pageSize * (@CurPage -1) + 1 = 20*(3-1) + 1 = 41
    正是因为ID是不连续的所以才需要用第一个语句来定位,如果是连续的那还用第一条语句做什么呢?

    举各少量数据的例子:
    假设有10条记录,ID是:1000,500,320,205,115,110,95,68,4,1。这回不写连续的了免的误会
    一页显示两条记录,现在要显示第三页,那么第三页的id就是 115,110

    先看第一条语句
    select top 5 @id=ID from table order by ID desc
    不知道大家有没有看懂这句,这时print @id 得到的结果是 115。

    再看第二条语句
    select top 2 * from table where ID <=115 order by ID desc
    这时的记录集就是 115,110,也就是我们所需要的记录了。


    注意:不需要连续的ID,也不局限只能按ID排序,你可以换成ReplyDate(最后回复时间)字段,
    当然了declare @id int 要改成 declare @id datetime

    这里的ID 是主键,唯一标识记录的字段,它本身就是一种索引,而且是效率最高的索引。

    A.唯一标识记录的字段的值怎么能随意改动呢,那不乱套了吗?

    B.主键是最快的索引,可能你还没有意识到(一开始我就不知道,学了SQL很久以后才知道的),如果你的算法用它作为排序字段,那么速度会很快,会比用其他字段(没有索引的字段)排序快很多。

    C.用ReplyDate(最后回复时间)来排序,那么就必须给他建立索引(在海量数据的情况下),否则会超时的。


    D.建立索引后,再执行添加、修改、删除会对数据库带来灾难性的折磨??
    一开始我也是这么认为的,但是为了能够翻页,不得不加索引。
    但是接下来的事实确打消了我的顾虑

    先来看添加。
    100万条记录是怎么弄出来的?大家可以看到帖子里有很多标题一样的主题,对了是复制出来的。
    我先加了16条记录,然后加上了索引。注意在insert into 之前就已经建立好了索引!

    接下来就是insert into table (...) select ... from table 影响的行数:
    16、32、64、128、256、512、1024、2048、4096、8192、16384、32768、65536、
    131072、262144、524288 很快记录就达到了100完了。
    最后一次也只不过一两分钟(具体的时间忘记了,反正是很快了)。
    同时,论坛也提供了发贴的功能,只是在批量添加记录的时候,把一些记录的最后回复时间弄成了2006年,
    所以,你发的帖子不会显示在第一页。但是你可以看到,执行时间是很快的。

    可见添加的时候是不成问题的,索引是倒序排列的,所以影响的行数绝对没有你想象的那么多。

    再来看修改
    看了sp1234的回复,加了修改的功能,只是为了测试,所以呢可以修改标题、最后发表时间、分组ID。
    为什么可以修改这几个字段呢?标题是普通字段,最后发表时间和分组ID是索引字段。
    修改这几个字段需要的时间都是很快的,在最后回复时间的右面有 [改] [删] 字样,大家可以试一试。
    同样,修改的时候,影响的行数也不是很多。

    最后看删除
    不多说了,论坛提供了这个功能,试一下就知道了。另外,删除的时候,不用重新建立一遍索引吧?


    在来说一下使用范围吧。
    首先呢这只是一种方法,而不是一个通用的存储过程,也就是说要根据情况作适当的修改。
    最佳使用环境:
    单表,单排序字段,可以利用索引。
    注意事项:
    排序字段不必连续,最好使用int、datetime类型的字段,字符串型的字段没有试过,效果可能会略差。
    表可以没有主键,但是对于海量数据的情况下,必须建立合理的索引。

    有一个比较致命的限制,大家好像都没有发现,那就是排序字段的重复性,
    最好是没有重复的,但不是说绝对不能有重复的记录,有不要紧,只要不跨页就行,跨页的话就会挤掉若干条记录,
    用时间字段来排序,发生重复的记录的可能性就很小了。


    扩展性
    bingbingcha(不思不归,不孟不E,原来是头大灰狼) 的回复很精彩
    -----------------
    这样的技巧在SQL区都讨论过了..速度是很快的..但是满足不了需求的..实用性太差了..现在的企业需要用到分页的大部分都是多表查询..单表分页满足不了需求的..

    这个存储过程可以扩展..用临时表+楼主的方法..是个不错的选择..
    -----------------

    对于多表关联查询,有两种方法,第一种就是bingbingcha说的 —— “用临时表+楼主的方法”,这是在海量数据的时候唯一可行的方法。
    但是在小数据量的时候,这么些就有一点繁琐,而且不容易归纳到通用的写法里。

    先来看一下查询语句据的写法:
    联合的
    SELECT a.ReplyID, a.TopicID
    FROM dbo.BBS_Reply a INNER JOIN
    dbo.BBS_body b ON a.BodyID = b.bodyID
    where a.ReplyID >10

    单表的

    SELECT ReplyID, TopicID
    FROM dbo.BBS_Reply
    where ReplyID >10

    有没有看到相同的地方:
    select 显示的字段
    from 表
    where 条件

    那么单表查询和多表查询有什么区别呢?
    至少有很多的多表(单字段排序)查询都是可用这种方式的。
    注意:我并没有说所有的多表(单字段排序)查询都可以用,看具体情况了。



    这是一个效率最高(需要合理的索引的帮忙),比较通用的翻页方法。不知道这次我有没有讲明白。

  • 相关阅读:
    解决PKIX:unable to find valid certification path to requested target 的问题
    Linux 上的常用文件传输方式介绍与比较
    用VNC远程图形化连接Linux桌面的配置方法
    红帽中出现”This system is not registered with RHN”的解决方案
    linux安装时出现your cpu does not support long mode的解决方法
    CentOS SSH配置
    es6扩展运算符及rest运算符总结
    es6解构赋值总结
    tortoisegit安装、clon、推送
    es6环境搭建
  • 原文地址:https://www.cnblogs.com/jyk/p/478025.html
Copyright © 2011-2022 走看看