一个多对多关系表的数据分页显示问题sp_cursoropen 比较 临时表方式

 '=============================

各种分页存储过程谁好谁坏,其实没有绝对,而各类存储过程快慢表现的差别,其实质是查询过程中能否有效果利用索引的差别,尤其是聚集索引的利用

'=============================

 一个新闻模块,需要专题功能, 文章与专题是多对多关系,表数据结构如果下:
文章表:Article(Id,ClassId,Title....)
专题表:Special(SpecialId,Title,Url...)
文章专题表:ArticleSpecial(ArticleId,SpecialId,SectionId,OrderId) -- SectionId版块,

管理后台需要如下分页列表:
文章编号 文章标题 专题名称 版块号 排序号
对应:R(Article.Id,Article.Title,Special.Title,ArticleSpecial.SectionId,ArticleSpecial.OrderId)
另外在Asp.net 中采用ObjectDataSource + GridView , GridView中需要支持ArticleSpecial表的OrderId,SectionId,ArticleId等字段排序显示(每次只允许一个).

=============================================
目前在MSSQL2000下使用的分页存储过程基本就那么几个, 由于这里设计关系表(ArticleSpecial)是使用复合主键,应次Select Max(key) 方式, Set Rowcount  等方式不能用, 只能使用插入临时表方式跟服务器端游标(使用sp_cursoropen 等)方式,---注意这里不考虑网ArticleSpecial表中加入额外主键,加入额外主键后,分页管理到是可以方便解决,但是考虑上面三个表inner join 操作的性能问题故把主键(默认设置为聚集索引)分配给ArticleSpecial表的 SpecialD跟ArticleId列.
sp_cursoropen这些游标操作API,虽然在MS的帮助文档里找不到,但是如果使用asp + ADO 进行数据库操作时,其实可以看到(SQL跟踪)ADO调用的也就是这些游标操作API.

===============================
--//游标分页存储过程

ALTER        procedure QueryByCursor
@sqlstr nvarchar(4000), --查询字符串
@StartRows int, --第N页
@pageSize int, --每页行数
@Total int OUTPUT
As
Set nocount on
Declare @P1 int --P1是游标的id

If @StartRows<=0
  Begin
    Set @StartRows=1
  End
Else
  Begin
    Set @StartRows=@StartRows
  End

exec sp_cursoropen @P1 output,@sqlstr,@scrollopt=1,@ccopt=1,@Total=@Total output
exec sp_cursorfetch @P1,16,@startRows,@pagesize
exec sp_cursorclose @P1
Return @Total
Set NoCount off

//-- asp.net 中对应的操作

//1.帮助函数,对QueryByCursor进行封装,注意这里反回的是第二个Table,第一个为空
        public static DataSet Query(string connStr,int startRows,int pageSize,string sql,out int total)
        {
            SqlParameter sqlTotal = new SqlParameter("@total", 0);
            total = 0;
            sqlTotal.Direction = ParameterDirection.Output;
            SqlParameter[] parameters ={
                new SqlParameter("@startRows",startRows),
                new SqlParameter("@pageSize",pageSize),
                new SqlParameter("@sqlstr",sql),
                sqlTotal
            };
            DataSet ds = SqlHelper.ExecuteDataset(connStr, CommandType.StoredProcedure, "QueryByCursor", parameters);
            if (ds.Tables.Count >= 2)
            {
                ds.Tables.RemoveAt(0); //第一个为空
            }
            total = Convert.ToInt32(sqlTotal.Value);
            return ds;
        }

//过滤以及一些拼接操作,最后调用上面的帮助函数Query(...)

      public static DataSet SelectByCursor(int startRowIndex, int maximumRows, int specialId, string key, string sortExpress)
        {
            key = StringHelper.FilterTSQL(key);
            sortExpress = StringHelper.FilterTSQL(sortExpress);
            string sql = @" Select  a2s.*,a.Title,s.Title as SpecialName
  From  
   ArticleSpecial a2s
   inner join
   Article a on a.Id=a2s.ArticleId
   Inner join
   Special s On s.SpecialId=a2s.SpecialId ";
            string where = "Where 1=1 ";
            string order = "";
            if (specialId > 0)
            {
                where += " And a2s.SpecialId=" + specialId;
            }
            if (!string.IsNullOrEmpty(key))
            {
                where += " And  (a.Title like '%" + key + "%' Or a.Keys like '%" + key + "%')";
            }
            if (!string.IsNullOrEmpty(sortExpress))
            {
                order = " Order By a2s." + sortExpress;
            }
            sql += where + order;
           return DBH.Query(DBH.NewsDB, startRowIndex, maximumRows, sql, out _RecordCount);

        }

 ========================================

 使用插入临时表的方式:

//--存储过程入下:

---根据指定条件返回分页后的文章专题数据
---第二节的多个inner join 语句始终使用 t开始
ALTER          Proc ArticleSpecial_Query
   @starRows int =0,
   @pageSize int =20,
   @SpecialId int,
   @Key nvarchar(100),
   @OrderColumn nvarchar(50)
 As

Declare @SQL nvarchar(4000)
Declare @where nvarchar(200)
Declare @order nvarchar
Set @where=' 1=1 '
--专题不为0
If @specialId>0
  Begin
   Set @Where=@Where + ' And a2s.SpecialId=' + cast(@SpecialId as nvarchar(50))
  End
--关键字不为空
If @Key !=''
  Begin
   Set @where=@where + ' And (a.Title like ''%' +@key +'%'' Or a.Keys like ''%' + @key + '%'')'
  End

If @OrderColumn=''
  Begin
   Set @OrderColumn='ArticleId'
  End
 
Set @SQL='
  Set Nocount ON
  Create table #t (nid bigint identity(1,1)  Primary Key,ArticleID int,SpecialID int)

  Insert Into #t(ArticleId,SpecialId)
  Select a2s.ArticleId,a2s.SpecialId
  From
  ArticleSpecial a2s inner join Article a on a.Id=a2s.ArticleId
  Where '+ @where +'
  Order By a2s.' + @OrderColumn +'

  Select  a2s.*,a.Title,s.Title as SpecialName
  From  
   ArticleSpecial a2s
   inner join
   Article a on a.Id=a2s.ArticleId
   Inner join
   Special s On s.SpecialId=a2s.SpecialId
   Inner join [#t] t 
   On a2s.ArticleID=t.ArticleID And t.SpecialID=a2s.SpecialID
   Where nid >' + cast(@starRows as nvarchar) +' and nid <= ' + cast( (@starRows +@pageSize) as nvarchar) +'

  Set NoCount Off
  Drop Table #t
    
'
Exec(@SQL

=======================================

//测试代码: Article表中有10万条数据, Special表中有200表记录(200个专题),ArticleSpecial表中有1万2千条记录,
//这个数据级别基本符合公司5到10年的需求了

        private delegate DataSet TestFunction(int startIndex, int pageSize, int specialId, string key, string sortExpress);
        private void button1_Click(object sender, EventArgs e)
        {
            button1.Enabled = false;
            TestFunction fun1 = new TestFunction(ArticleSpecialDAL.Select);
            TestFunction fun2 = new TestFunction(ArticleSpecialDAL.SelectByCursor);
           
            Stopwatch sw1 = new Stopwatch();
            Stopwatch sw2 = new Stopwatch();

            #region 方式1
            sw1.Start();
            for (int i = 0; i < 12000; i = i + 500)
            {
                DataSet ds = DoFunction(fun1, i, 20, 0, "", "ArticleId");

            }
            sw1.Stop();
            #endregion
            #region 方式2
            sw2.Start();
            for (int i = 0; i < 12000; i = i + 500)
            {
                DataSet ds = DoFunction(fun2, i, 20, 0, "", "ArticleId");

            }
            sw2.Stop();
            #endregion

           
            label1.Text = "Select方式:" + sw1.ElapsedMilliseconds.ToString() +Environment.NewLine
                          + "SelectByCursor:" + sw2.ElapsedMilliseconds.ToString();
            button1.Enabled = true;
              
        }
        private DataSet DoFunction(TestFunction fun,int startIndex,int pageSize ,int specialId ,string key ,string sortExpress)
        {
            return fun(startIndex, pageSize, specialId, key, sortExpress);
        }

//============================
 测试结果:
使用服务器游标方式比使用插入临时表方式快了将近10倍,插入临时表方式完成上面操作一般需要2万毫秒,而使用游标方式只需要2千毫秒,当然网上经常可以看到的说法是游标方式比插入临时表方式要慢, 这个主要是大家面对的问题不同,数据量也不同,我上面的测试结果是在ArticleSpecial表有12000条记录下得出的,当数据量继续整加时是什么情况我没做测试.
另外发现当传入key关键字跟key保持为空时,插入临时表方式运行时间差不多,而使用游标方式者区别很到,另外两种方式的CUP站用率都比为空时高很多(我这达到50%,为空时10%),可见字符比较操作占用大量的cup操作时间

原文地址:https://www.cnblogs.com/wdfrog/p/1497464.html