• 一个多对多关系表的数据分页显示问题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操作时间

  • 相关阅读:
    C++ Primer Plus(三)
    C++ Primer Plus(二)
    C++ Primer Plus(一)
    C Primer Plus(三)
    C++ 函数重载,函数模板和函数模板重载,选择哪一个?
    Spring IoC 公共注解详解
    Spring IoC @Autowired 注解详解
    Spring IoC 容器的扩展
    Spring IoC bean 的初始化
    Spring IoC 属性赋值阶段
  • 原文地址:https://www.cnblogs.com/wdfrog/p/1497464.html
Copyright © 2020-2023  润新知