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

  • 相关阅读:
    轻重搭配
    EF的优缺点
    使用bootstrap-select有时显示“Nothing selected”
    IIS发布 HTTP 错误 500.21
    js添加的元素无法触发click事件
    sql server查看表是否死锁
    sql server把一个库表的某个字段更新到另一张表的相同字段
    SQLSERVER排查CPU占用高的情况
    SQL server中如何按照某一字段中的分割符将记录拆成多条
    LINQ to Entities does not recognize the method 'System.DateTime AddDays(Double)' method, and this method cannot be translated into a store expression.
  • 原文地址:https://www.cnblogs.com/wdfrog/p/1497464.html
Copyright © 2011-2022 走看看