zoukankan      html  css  js  c++  java
  • 千万级分页存储过程

    这两天测试了前几天写的SQL2005专用分页的存储过程,当数据量达到2千多万的时候,效率相当的低,每次执行都要8秒左右(CPU:Q6600)。不过在2百多万数据量的情况下性能还是蛮不错的,在网上找了找,发现这下面的这两个,其实还是一个,不过后面那个是灵活了许多,仅供参考。
    复制 保存/******
    Object: StoredProcedure [dbo].[GetRecordFromPage]  
    Script Date: 07/23/2008 18:42:05
    ******/
    SET ANSI_NULLS ON
    GO
    SET QUOTED_IDENTIFIER ON
    GO
    /*
    函数名称: GetRecordFromPage
    函数功能: 获取指定页的数据
    参数说明: @tblName      包含数据的表名
               @fldName      关键字段名
               @PageSize     每页记录数
               @PageIndex    要获取的页码
               @OrderType    排序类型, 0 - 升序, 1 - 降序
               @strWhere     查询条件 (注意: 不要加 where)
    */
    ALTER PROCEDURE [dbo].[GetRecordFromPage]
        @tblName      varchar(255),       -- 表名
        @fldName      varchar(255),       -- 字段名
        @PageSize     int = 10,           -- 页尺寸
        @PageIndex    int = 1,            -- 页码
        @OrderType    bit = 0,            -- 设置排序类型, 非 0 值则降序
        @strWhere     varchar(2000) = '' -- 查询条件 (注意: 不要加 where)
    AS

    declare @strSQL   varchar(6000)       -- 主语句
    declare @strTmp   varchar(1000)       -- 临时变量
    declare @strOrder varchar(500)        -- 排序类型

    if @OrderType != 0
    begin
        set @strTmp = '<(select min'
        set @strOrder = ' order by [' + @fldName +'] desc'
    end
    else
    begin
        set @strTmp = '>(select max'
        set @strOrder = ' order by [' + @fldName +'] asc'
    end

    set @strSQL = 'select top ' + str(@PageSize) + ' * from ['
        + @tblName + '] where [' + @fldName + ']' + @strTmp + '(['
        + @fldName + ']) from (select top ' + str((@PageIndex-1)*@PageSize) + ' ['
        + @fldName + '] from [' + @tblName + ']' + @strOrder + ') as tblTmp)'
        + @strOrder

    if @strWhere != ''
        set @strSQL = 'select top ' + str(@PageSize) + ' * from ['
            + @tblName + '] where [' + @fldName + ']' + @strTmp + '(['
            + @fldName + ']) from (select top ' + str((@PageIndex-1)*@PageSize) + ' ['
            + @fldName + '] from [' + @tblName + '] where ' + @strWhere + ' '
            + @strOrder + ') as tblTmp) and ' + @strWhere + ' ' + @strOrder

    if @PageIndex = 1
    begin
        set @strTmp = ''
        if @strWhere != ''
            set @strTmp = ' where (' + @strWhere + ')'

        set @strSQL = 'select top ' + str(@PageSize) + ' * from ['
            + @tblName + ']' + @strTmp + ' ' + @strOrder
    end

    exec (@strSQL)
    这是我目前见过效率最高的,不过它的order by 是不是有点儿问题,不能指定某一个,还有没有返回总记录数。于是有了下面这个改进的。

    如果需要总页数还可以再改一下,不过目前已经足够用了,当然,当达到千万数据量后,比上面的效率上稍微低了一点点,不过还不算太坏,

    比较推荐下面这个。
    复制 保存/******
    Object: StoredProcedure [dbo].[usp_GetRecordFromPage]   
    Script Date: 07/23/2008 18:42:37
    ******/
    SET ANSI_NULLS ON
    GO
    SET QUOTED_IDENTIFIER ON
    GO
    CREATE       PROCEDURE [dbo].[usp_GetRecordFromPage]
        @tblName       varchar(1000),        -- 表名
        @SelectFieldName    varchar(4000),              -- 要显示的字段名(不要加select)
        @strWhere       varchar(4000),              -- 查询条件(注意: 不要加 where)
        @OrderFieldName      varchar(255),               -- 排序索引字段名
        @PageSize       int ,                 -- 页大小
        @PageIndex      int = 1,                  -- 页码
        @iRowCount      int output,                 -- 返回记录总数
        @OrderType      bit = 0                  -- 设置排序类型, 非 0 值则降序
              
    AS

    declare @strSQL    varchar(4000)       -- 主语句
    declare @strTmp    varchar(4000)        -- 临时变量
    declare @strOrder varchar(400)        -- 排序类型
    declare @strRowCount    nvarchar(4000)      -- 用于查询记录总数的语句

    set @OrderFieldName=ltrim(rtrim(@OrderFieldName))

    if @OrderType != 0
    begin
        set @strTmp = '<(select min'
        set @strOrder = ' order by ' + @OrderFieldName +' desc'
    end
    else
    begin
        set @strTmp = '>(select max'
        set @strOrder = ' order by ' + @OrderFieldName +' asc'
    end

    set @strSQL = 'select top ' + str(@PageSize) + @SelectFieldName+' from '
        + @tblName + ' where ' + @OrderFieldName + @strTmp + '('
        + right(@OrderFieldName,len(@OrderFieldName)-charindex('.',@OrderFieldName)) + ') from (select top ' + str((@PageIndex-1)*@PageSize)
        + @OrderFieldName + ' from ' + @tblName + @strOrder + ') as tblTmp)'
        + @strOrder

    if @strWhere != ''
        set @strSQL = 'select top ' + str(@PageSize) + @SelectFieldName+' from '
            + @tblName + ' where ' + @OrderFieldName + @strTmp + '('
            + right(@OrderFieldName,len(@OrderFieldName)-charindex('.',@OrderFieldName)) + ') from (select top ' + str((@PageIndex-1)*@PageSize)
            + @OrderFieldName + ' from ' + @tblName + ' where ' + @strWhere + ' '
            + @strOrder + ') as tblTmp) and ' + @strWhere + ' ' + @strOrder

    if @PageIndex = 1
    begin
        set @strTmp = ''
        if @strWhere != ''
            set @strTmp = ' where ' + @strWhere

        set @strSQL = 'select top ' + str(@PageSize) + @SelectFieldName+' from '
            + @tblName + @strTmp + ' ' + @strOrder
    end

    exec(@strSQL)

    if @strWhere!=''
    begin
    set @strRowCount = 'select @iRowCount=count(*) from ' + @tblName+' where '+@strWhere
    end
    else
    begin
    set @strRowCount = 'select @iRowCount=count(*) from ' + @tblName
    end

    exec sp_executesql @strRowCount,N'@iRowCount int out',@iRowCount out

    在项目中,我们经常遇到或用到分页,那么在大数据量(百万级以上)下,哪种分页算法效率最优呢?我们不妨用事实说话。


    测试环境

    硬件:CPU 酷睿双核T5750 内存:2G
    软件:Windows server 2003    +   Sql server 2005

    OK,我们首先创建一数据库:data_Test,并在此数据库中创建一表:tb_TestTable
    复制 保存create database data_Test --创建数据库data_Test
    GO
    use data_Test
    GO
    create table tb_TestTable   --创建表
    (
        id int identity(1,1) primary key,
        userName nvarchar(20) not null,
        userPWD nvarchar(20) not null,
        userEmail nvarchar(40) null
    )
    GO
    然后我们在数据表中插入2000000条数据:
    复制 保存--插入数据
    set identity_insert tb_TestTable on
    declare @count int
    set @count=1
    while @count<=2000000
    begin
        insert into tb_TestTable(id,userName,userPWD,userEmail) values
                           (@count,'admin','admin888','lli0077@yahoo.com.cn')
        set @count=@count+1
    end
    set identity_insert tb_TestTable off

    我首先写了五个常用存储过程:

    1,利用select top 和select not in进行分页,具体代码如下:
    复制 保存create procedure proc_paged_with_notin --利用select top and select not in
    (
        @pageIndex int, --页索引
        @pageSize int    --每页记录数
    )
    as
    begin
        set nocount on;
        declare @timediff datetime --耗时
        declare @sql nvarchar(500)
        select @timediff=Getdate()
        set @sql='select top '+str(@pageSize)+' * from tb_TestTable where(ID not in(select top '+str(@pageSize*@pageIndex)+' id from tb_TestTable order by ID ASC)) order by ID'
        execute(@sql) --因select top后不支技直接接参数,所以写成了字符串@sql
        select datediff(ms,@timediff,GetDate()) as 耗时
        set nocount off;
    end
    2,利用select top 和 select max(列键)
    复制 保存create procedure proc_paged_with_selectMax --利用select top and select max(列)
    (
        @pageIndex int, --页索引
        @pageSize int    --页记录数
    )
    as
    begin
    set nocount on;
        declare @timediff datetime
        declare @sql nvarchar(500)
        select @timediff=Getdate()
        set @sql='select top '+str(@pageSize)+' * From tb_TestTable where(ID>(select max(id) From (select top '+str(@pageSize*@pageIndex)+' id From tb_TestTable order by ID) as TempTable)) order by ID'
        execute(@sql)
        select datediff(ms,@timediff,GetDate()) as 耗时
    set nocount off;
    end
    3,利用select top和中间变量--此方法因网上有人说效果最佳,所以贴出来一同测试
    复制 保存create procedure proc_paged_with_Midvar --利用ID>最大ID值和中间变量
    (
        @pageIndex int,
        @pageSize int
    )
    as
        declare @count int
        declare @ID int
        declare @timediff datetime
        declare @sql nvarchar(500)
    begin
    set nocount on;
        select @count=0,@ID=0,@timediff=getdate()
        select @count=@count+1,@ID=case when @count<=@pageSize*@pageIndex then ID else @ID end from tb_testTable order by id
        set @sql='select top '+str(@pageSize)+' * from tb_testTable where ID>'+str(@ID)
        execute(@sql)
        select datediff(ms,@timediff,getdate()) as 耗时
    set nocount off;
    end
    4,利用Row_number() 此方法为SQL server 2005中新的方法,利用Row_number()给数据行加上索引
    复制 保存create procedure proc_paged_with_Rownumber --利用SQL 2005中的Row_number()
    (
        @pageIndex int,
        @pageSize int
    )
    as
        declare @timediff datetime
    begin
    set nocount on;
        select @timediff=getdate()
        select * from (select *,Row_number() over(order by ID asc) as IDRank from tb_testTable) as IDWithRowNumber where IDRank>@pageSize*@pageIndex and IDRank<@pageSize*(@pageIndex+1)
        select datediff(ms,@timediff,getdate()) as 耗时
    set nocount off;
    end
    5,利用临时表及Row_number
    复制 保存create procedure proc_CTE --利用临时表及Row_number
    (
        @pageIndex int, --页索引
        @pageSize int    --页记录数
    )
    as
        set nocount on;
        declare @ctestr nvarchar(400)
        declare @strSql nvarchar(400)
        declare @datediff datetime
    begin
        select @datediff=GetDate()
        set @ctestr='with Table_CTE as
                    (select ceiling((Row_number() over(order by ID ASC))/'+str(@pageSize)+') as page_num,* from tb_TestTable)';
        set @strSql=@ctestr+' select * From Table_CTE where page_num='+str(@pageIndex)
    end
        begin
            execute sp_executesql @strSql
            select datediff(ms,@datediff,GetDate())
        set nocount off;
        end
    OK,至此,存储过程创建完毕,我们分别在每页10条数据的情况下在第2页,第1000页,第10000页,第100000页,第199999页进行测试,耗时单位:ms 每页测试5次取其平均值
    存过 第2页耗时 第1000页 第10000页 第100000页 第199999页 效率排行
    1用not in 0ms 16ms 47ms 475ms 953ms 3
    2用select max 5ms 16ms 35ms 325ms 623ms 1
    3中间变量 966ms 970ms 960ms 945ms 933ms 5
    4row_number 0ms 0ms 34ms 365ms 710ms 2
    5临时表 780ms 796ms 798ms 780ms 805ms 4


  • 相关阅读:
    CCF-CSP201703-3 Markdown
    HDU1008 Elevator
    Java使用Preconditions.checkNotNull(.....)优雅地判空对象, 并处理可能的NullPointerException异常
    StringUtils的isBlank()方法
    java.math.RoundingMode 几个参数详解
    把String集合数据去"[ ]" 转成list和把String集合数据去除[ "" ,""] 转成list 工具类
    BeanUtils.copyProperties(复制对象属性方法)
    关于 mybatis 中 in 写法,<foreach collection="xxx" item="xxx" index="index" open="(" separator="," close=")"> 参数详解
    JAVA_collection_集合相关知识点(二、获取集合对象中某个属性的集合——CollectionUtils.collect()方法)
    @AllArgsConstructor @NoArgsConstructor
  • 原文地址:https://www.cnblogs.com/zzxap/p/2175940.html
Copyright © 2011-2022 走看看