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


  • 相关阅读:
    在WCF中使用Flag Enumerations
    WCF开发教程资源收集
    [转]WCF 4 安全性和 WIF 简介
    Asp.Net Web API 2 官网菜鸟学习系列导航[持续更新中]
    Asp.Net Web API 2第十八课——Working with Entity Relations in OData
    Asp.Net Web API 2第十七课——Creating an OData Endpoint in ASP.NET Web API 2(OData终结点)
    Asp.Net Web API 2第十六课——Parameter Binding in ASP.NET Web API(参数绑定)
    Asp.Net Web API 2第十五课——Model Validation(模型验证)
    函数 生成器 生成器表达式
    函数的进阶
  • 原文地址:https://www.cnblogs.com/zzxap/p/2175940.html
Copyright © 2011-2022 走看看