asp.net安全、实用、简单的大容量存储过程分页

  基本上我下面要讲述的侧重点是如何使用,因为其实分页存储过程网上很多,如果你懒得找,那么可以直接使用下面这个我经过测试,并通过修改,网上评价都比较高的分页存储过程。

  这个分页主要优点如下:

  1、大容量下的数据分页,我的测试数据是520W。

  2、我结合aspnetpager控件,使得使用起来更加方便。

  3、为了结构清晰,实用3层。

  4、安全,你就放心的用吧。SQL注入的问题在这里你可以放心了。网上有文章说只要存储过程是用sql拼接的就存在sql注入的问题,并且直接在sql查询分析器中测试了注入的情况。其实是不对的,采用存储过程和参数化的提交语句并没有sql注入的问题。因为它进数据库的时候会有替换的过程。

  准备工作:

  1、直接使用一个DB库,数据访问层基类,用它返回一个dataset对象,使用的时候我们只需要类似下面的语句就可以返回一个dataset对象。

  

复制代码 代码如下:

  sosuo8.DBUtility.DbHelperSQL.RunProcedure("pagination",parameter,"userinfo");

  pagination是我在网上找的存储过程,我进行了修改的,主要是添加输出总记录数。这里总记录数也特意说一下,一般我们都是使用类似下面的语句:

  

复制代码 代码如下:

  select count(*) from sosuo8data

  这里我又要说两句,网上有网友说使用count(某列),例如count(userName)会比count(*)快也是不对,如果找的列不对,那么并不会比count(*)快。而count(*)会自动帮我们查找可以实现最快统计的那列,而其实在使用中,一般就是我们的那个主键id,count(id)是最快的。

  2、建立数据库data_test,建立两个表:

  (1)、userinfo这个表用来放数据。

  

复制代码 代码如下:

  CREATE TABLE [dbo].[userinfo](

  [id] [int] IDENTITY(1,1) NOT NULL,

  [userName] [nchar](50) COLLATE Chinese_PRC_CI_AS NULL,

  [userWebName] [nchar](50) COLLATE Chinese_PRC_CI_AS NULL,

  [createDate] [datetime] NULL CONSTRAINT [DF_userinfo_createDate] DEFAULT (getdate()),

  CONSTRAINT [PK_userinfo] PRIMARY KEY CLUSTERED

  (

  [id] ASC

  )WITH (IGNORE_DUP_KEY = OFF) ON [PRIMARY]

  ) ON [PRIMARY]

  id是自递增,且是聚合索引。OK?开始往数据库中添加520W条记录:

  

复制代码 代码如下:

  --by 阿会楠 2009-4-5

  set identity_insert userinfo on -- 标识可以插入自递增列

  declare @count int

  declare @date datetime

  set @count=1

  set @date = '2009-4-5 00:00:00'

  while @count<=5200000

  begin

  insert into userinfo(id,userName,userWebName,createDate) values(@count,'阿会楠','sosuo8.com',@date)

  set @count=@count+1

  set @date=@date+'00:00:01'--加一秒,避免重复,否则会造成分页不准确的情况,排序的字段切忌不要出现过多重复值

  end

  set identity_insert userinfo off

  如果你的电脑配置比较一般,千万不要尝试,否则可能会当机;如果没当机,那耐心等下,插入这么多条记录需要一点时间。

  (2)tmp表用来存放无搜索条件时的总记录数。这里我要说下,为什么需要一个表用来专门存放总记录数,总记录数你可以在后台每隔一段时间就去更新一次,把最新的总记录数写进去。否则的话,520W的记录你每次都要用count(id)那么耗费的时间也不少。

asp.net安全、实用、简单的大容量存储过程分页

  建表的语句如下:

  

复制代码 代码如下:

  CREATE TABLE [dbo].[tmp](

  [id] [int] IDENTITY(1,1) NOT NULL,

  [rowCount_tmp] [int] NULL,

  [table_tmp] [varchar](255) COLLATE Chinese_PRC_CI_AS NULL,

  CONSTRAINT [PK_tmp] PRIMARY KEY CLUSTERED

  (

  [id] ASC

  )WITH (IGNORE_DUP_KEY = OFF) ON [PRIMARY]

  ) ON [PRIMARY]

  这里不得不提一下,为什么只要取得表的行总记录数,而不在存储过程里面把分页都算好。因为我们就将采用aspnetpager这个控件,这个控件我们只需要传入3个值,函数定义如下:

  

复制代码 代码如下:

  public DataSet GetList(int PageIndex, string strWhere,ref int rowCount)

  

复制代码 代码如下:

  PageIndex:当前页,对应aspnetpager中的CurrentPageIndex

  strWhere:搜索的条件,这篇文章将不重点讲搜索部分。所以代码中用string.Empty,大容量下的数据搜索那需要另写一篇文章来说明。

  rowCount:总记录数,由存储过程重新返回。

  开始测试:

  在BLL层(业务逻辑层),DAL层(数据访问层),DB(访问层基类),WEB(网站),关于3层结构这里就不介绍了,大家可以看看我前面写过的文章《.net三层结构初探》 。

  先添加存储过程:

  

复制代码 代码如下:

  --阿会楠根据网络上的代码进行了修改,版权归原作者所有2009-4-5

  --修改输出总记录数

  create PROCEDURE [dbo].[pagination]

  @tblName varchar(255), -- 表名

  @strGetFields varchar(1000), -- 需要返回的列

  @fldName varchar(255), -- 排序的字段名

  @PageSize int, -- 页尺寸

  @PageIndex int, -- 页码

  @OrderType bit, -- 设置排序类型, 非 0 值则降序

  @strWhere varchar(1500), -- 查询条件 (注意: 不要加 where)

  @rowCount int output --查询到的记录数

  AS

  declare @strSQL varchar(5000) -- 主语句

  declare @strTmp varchar(110) -- 临时变量

  declare @strOrder varchar(400) -- 排序类型

  declare @strCountTmp nvarchar(100) --记录数

  declare @timediff datetime --耗时

  begin

  select @timediff = Getdate()

  if @strWhere !=''

  set @strCountTmp = 'select @rowCount = count(*) from [' + @tblName + '] where '+@strWhere

  else

  set @strCountTmp = 'select @rowCount = rowCount_tmp from tmp where (table_tmp = ''' + @tblName +''')'

  exec sp_executesql @strCountTmp,N'@rowCount int out',@rowCount out --输出总记录数

  if @OrderType != 0

  begin

  set @strTmp = '<(select min'

  set @strOrder = ' order by ' + @fldName +' desc'

  --如果@OrderType不是0,就执行降序,这句很重要!

  end

  else

  begin

  set @strTmp = '>(select max'

  set @strOrder = ' order by ' + @fldName +' asc'

  end

  if @PageIndex = 1

  begin

  if @strWhere != ''

  set @strSQL = 'select top ' + str(@PageSize) +' '+@strGetFields+ ' from [' + @tblName + '] where ' + @strWhere + ' ' + @strOrder

  else

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

  --如果是第一页就执行以上代码,这样会加快执行速度

  end

  else

  begin

  --以下代码赋予了@strSQL以真正执行的SQL代码

  set @strSQL = 'select top ' + str(@PageSize) +' '+@strGetFields+ ' 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) +' '+@strGetFields+ ' from ['

  + @tblName + '] where [' + @fldName + ']' + @strTmp + '(['

  + @fldName + ']) from (select top ' + str((@PageIndex-1)*@PageSize) + ' ['

  + @fldName + '] from [' + @tblName + '] where ' + @strWhere + ' '

  + @strOrder + ') as tblTmp) and ' + @strWhere + ' ' + @strOrder

  end

  end

  exec (@strSQL)

  select datediff(ms,@timediff,GetDate()) as runtime

  这个SQL存储过程语句非常的好,但是不足的地方就是如果你的条件语句中含有'(单引号)那就很郁闷了,十分的麻烦。你可以试下改动上面的相关代码如下:

  

复制代码 代码如下:

  replace(@strWhere,'''','''''')

  不放心自己测试下吧。

  

复制代码 代码如下:

  USE [data_Test]

  GO

  DECLARE @return_value int,

  @rowCount int

  EXEC @return_value = [dbo].[pagination]

  @tblName = 'userinfo',

  @strGetFields = N'id',

  @fldName = N'id',

  @PageSize = 10,

  @PageIndex = 300,

  @OrderType = 0,

  @strWhere = null,

  @rowCount = @rowCount OUTPUT

  SELECT @rowCount as N'@rowCount'

  SELECT 'Return Value' = @return_value

  GO

  最主要的DAL层代码:

  

复制代码 代码如下:

  public DataSet GetList(int PageIndex, string strWhere,ref int rowCount)

  {

  SqlParameter[] parameter = sosuo8.DBUtility.DbHelperSQL.pagePara();

  parameter[0].Value = "userinfo";

  parameter[1].Value = "id,userName,userWebName,createDate";

  parameter[2].Value = "id";

  parameter[3].Value = 10;

  parameter[4].Value = PageIndex;

  parameter[5].Value = 0;

  parameter[6].Value = strWhere;

  parameter[7].Direction = ParameterDirection.Output;//声明为输出类型

  DataSet ds = sosuo8.DBUtility.DbHelperSQL.RunProcedure("pagination",parameter,"userinfo");

  rowCount = Convert.ToInt32(parameter[7].Value);

  return ds;

  }

  全部代码:

  

复制代码 代码如下:

  public DataSet GetList(int iPageSize, int iPageIndex, string strWhere, ref int iCount)

  {

  SqlParameter[] parameters ={

  new SqlParameter("@tblName",SqlDbType.VarChar,255),

  new SqlParameter("@strGetFields",SqlDbType.VarChar,1000),

  new SqlParameter("@fldName",SqlDbType.VarChar,255),

  new SqlParameter("@PageSize",SqlDbType.Int),

  new SqlParameter("@PageIndex",SqlDbType.Int),

  new SqlParameter("@OrderType",SqlDbType.Int),

  new SqlParameter("@strWhere",SqlDbType.NVarChar,2000),

  new SqlParameter("@rowCount",SqlDbType.Int),

  };

  parameters[0].Value = "News";

  parameters[1].Value = "newsID,classID,title,picUrl,videoUrl,writer,source,province,addTime,istop,state,keywords,recommend,hits,htmlPath,pic1,pic2,pic3";

  parameters[2].Value = "addTime";

  parameters[3].Value = iPageSize;

  parameters[4].Value = iPageIndex;

  parameters[5].Value = 1;

  parameters[6].Value = strWhere;

  parameters[7].Direction = ParameterDirection.Output;//声明为输出类型

  DataSet ds = sosuo8.DBUtility.DbHelperSQL.RunProcedure("GetRecordByPage", parameters, "News");

  iCount = Convert.ToInt32(parameters[7].Value);

  return ds;

  }

  default.aspx.cs代码

  

复制代码 代码如下:

  public partial class _Default : System.Web.UI.Page

  {

  protected void Page_Load(object sender, EventArgs e)

  {

  if (!Page.IsPostBack)

  {

  bind();

  }

  }

  private void bind()//绑定数据

  {

  this.anPager.PageSize = 10;//每页记录数

  this.anPager.AlwaysShow = true;//是否一直显示分页

  int rowCount = 0;//初始化记录数为0

  string wherestr = string.Empty;//搜索关键字,这部分后面我在讲讲优化

  sosuo8.BLL.bll_test bll = new sosuo8.BLL.bll_test();

  this.rpt.DataSource = bll.GetList(this.anPager.CurrentPageIndex,wherestr,ref rowCount);//rowCount在经过这个函数后,返回的是重新赋值的记录总数

  this.anPager.RecordCount = rowCount;

  this.rpt.DataBind();

  }

  protected void anPager_PageChanging(object src, Wuqi.Webdiyer.PageChangingEventArgs e)

  {

  this.anPager.CurrentPageIndex = e.NewPageIndex;

  bind();

  }

  }

  里面涉及到aspnetpager的使用,如果你还不会使用这个控件,可以自己看看有关教程。最终的界面如下:

asp.net安全、实用、简单的大容量存储过程分页

  令我十分高兴的是,在处理520W的记录时,它速度还在可以接受的范围内,不会出现超时的现象。而作为优化,可以在界面层中尽量少读数据。可以加上以page为参数页面输出缓存:

  

复制代码 代码如下:

  <%@ OutputCache Duration="360" VaryByParam="page" %>

  你也许并没有用过里面的一些控件,但是知道原理和方法我相信你也可以自由搭配你需要的东西,下面我仅上传部分比较重要的代码,对于需要的控件我也放在里面,至于建表测试那些大家慢慢研究吧!当前的存储过程只能针对一个字段排序,后面有时间我会修改成多字段排序

  打包下载地址