• SQL Server优化


    转:http://www.cnblogs.com/lyhabc/archive/2013/01/13/2858916.html

    SQL Server读懂语句运行的统计信息 SET STATISTICS TIME IO PROFILE ON

    对于语句的运行,除了执行计划本身,还有一些其他因素要考虑,例如语句的编译时间、执行时间、做了多少次磁盘读等。

    如果DBA能够把问题语句单独测试运行,可以在运行前打开下面这三个开关,收集语句运行的统计信息。

    这些信息对分析问题很有价值。

    1 SET STATISTICS TIME ON
    2 SET STATISTICS IO ON
    3 SET STATISTICS PROFILE ON

    SET STATISTICS TIME ON



    请先来看看SET STATISTICS TIME ON会返回什么信息。先运行语句:

     1 DBCC DROPCLEANBUFFERS
     2 --清除buffer pool里的所有缓存数据
     3 DBCC freeproccache
     4 GO
     5 
     6 --清除buffer pool里的所有缓存的执行计划
     7 SET STATISTICS TIME ON
     8 GO
     9 USE [AdventureWorks]
    10 GO
    11 SELECT DISTINCT([ProductID]),[UnitPrice] FROM [dbo].[SalesOrderDetail_test]
    12 WHERE [ProductID]=777
    13 GO
    14 SET STATISTICS TIME OFF
    15 GO

    除了结果集之外,SQLSERVER还会返回下面这两段信息

     1 SQL Server 分析和编译时间: 
     2    CPU 时间 = 15 毫秒,占用时间 = 104 毫秒。
     3 SQL Server 分析和编译时间: 
     4    CPU 时间 = 0 毫秒,占用时间 = 0 毫秒。
     5 
     6 (4 行受影响)
     7 
     8 SQL Server 执行时间:
     9    CPU 时间 = 171 毫秒,占用时间 = 1903 毫秒。
    10 SQL Server 分析和编译时间: 
    11    CPU 时间 = 0 毫秒,占用时间 = 0 毫秒。

    大家知道SQLSERVER执行语句是分以下阶段:分析-》编译-》执行

    根据表格的统计信息分析出比较合适的执行计划,然后编译语句,最后执行语句

    下面说一下上面的输出是什么意思:


    1、CPU时间 :这个值的含义指的是在这一步,SQLSERVER所花的纯CPU时间是多少。也就是说,语句花了多少CPU资源

    2、占用时间 :此值指这一步一共用了多少时间。也就是说,这是语句运行的时间长短,有些动作会发生I/O操作,产生了I/O等待,

    或者是遇到阻塞、产生了阻塞等待。总之时间用掉了,但是没有用CPU资源。所以占用时间比CPU时间长是很正常的 ,但是CPU时间是

    语句在所有CPU上的时间总和。如果语句使用了多颗CPU,而其他等待几乎没有,那么CPU时间大于占用时间也是正常的

    3、分析和编译时间:这一步,就是语句的编译时间。由于语句运行之前清空了所有执行计划,SQLSERVER必须要对他编译。

    这里的编译时间就不为0了。由于编译主要是CPU的运算,所以一般CPU时间和占用时间是差不多的。如果这里相差比较大,

    就有必要看看SQLSERVER在系统资源上有没有瓶颈了。

    这里他们是一个15毫秒,一个是104毫秒

    4、SQLSERVER执行时间: 语句真正运行的时间。由于语句是第一次运行,SQLSERVER需要把数据从磁盘读到内存里,这里语句的

    运行发生了比较长的I/O等待。所以这里的CPU时间和占用时间差别就很大了,一个是171毫秒,而另一个是1903毫秒

    总的来讲,这条语句花了104+1903+186=2193毫秒,其中CPU时间为15+171=186毫秒。语句的主要时间应该是都花在了I/O等待上

    现在再做一遍语句,但是不清除任何缓存

    1 SET STATISTICS TIME ON
    2 GO
    3 
    4 SELECT DISTINCT([ProductID]),[UnitPrice] FROM [dbo].[SalesOrderDetail_test]
    5 WHERE [ProductID]=777
    6 
    7 GO
    8 SET STATISTICS TIME OFF
    9 GO

    这次比上次快很多。输出时间统计信息是:

     1 SQL Server 分析和编译时间: 
     2    CPU 时间 = 0 毫秒,占用时间 = 0 毫秒。
     3 SQL Server 分析和编译时间: 
     4    CPU 时间 = 0 毫秒,占用时间 = 0 毫秒。
     5 
     6 (4 行受影响)
     7 
     8 SQL Server 执行时间:
     9    CPU 时间 = 156 毫秒,占用时间 = 169 毫秒。
    10 SQL Server 分析和编译时间: 
    11    CPU 时间 = 0 毫秒,占用时间 = 0 毫秒。

    由于执行计划被重用,“SQL分析和编译时间” CPU时间是0,占用时间是0

    由于数据已经缓存在内存里,不需要从磁盘上读取,SQL执行时间 CPU时间是156,占用时间这次和CPU时间非常接近,是169。

    这里省下运行时间1903-169=1734毫秒,从这里可以再次看出,缓存对语句执行性能起着至关重要的作用

    为了不影响其他测试,请运行下面的语句关闭SET STATISTICS TIME ON

    1 SET STATISTICS TIME OFF
    2 GO

    SET STATISTICS IO ON


    这个开关能够输出语句做的物理读和逻辑读的数目。对分析语句的复杂度有很重要的作用

    还是以刚才那个查询作为例子

    1 DBCC DROPCLEANBUFFERS
    2 GO
    3 SET STATISTICS IO ON
    4 GO
    5 
    6 SELECT DISTINCT([ProductID]),[UnitPrice] FROM [dbo].[SalesOrderDetail_test]
    7 WHERE [ProductID]=777
    8 GO

    他的返回是:

    1 (4 行受影响)
    2 表 'SalesOrderDetail_test'。扫描计数 5,逻辑读取 15064 次,物理读取 0 次,预读 15064 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。

    各个输出的含义是:


    :表的名称。这里的表就是SalesOrderDetail_test

    扫描计数:执行的扫描次数。按照执行计划,表格被扫描了几次。一般来讲大表扫描的次数越多越不好。唯一的例外是如果执行计划选择了并发运行,

    由多个thread线程同时做一个表的读取,每个thread读其中的一部分,但是这里会显示所有thread的数目。也就是有几个thread在并发做,

    就会有几个扫描。这时数目大一点没问题的。

    逻辑读取:从数据缓存读取的页数。页数越多,说明查询要访问的数据量就越大,内存消耗量越大,查询也就越昂贵。

    可以检查是否应该调整索引,减少扫描的次数,缩小扫描范围

    顺便说一下这个逻辑读取的统计原理:为什麽显示出来的结果的单位不是Page,也不是K或KB。SQLSERVER

    里在做读和写的时候,会运行到某一段特定的代码。每调用一次这个代码,Reads/Write就会加1。所以这个值比较大

    那语句一定做了比较多的I/O,但是不能通过这个值计算出I/O的绝对数量,这个值反映的是逻辑读写量不是物理读写量

    1 逻辑读取 15064 次

    物理读取:从磁盘读取的页数

    预读:为进行查询而预读入缓存的页数

    物理读取+预读:就是SQLSERVER为了完成这句查询而从磁盘上读取的页数。如果不为0,说明数据没有缓存在内存里。运行速度一定会受到影响

    LOB逻辑读取:从数据缓存读取的text、ntext、image、大值类型(varchar(max)、nvarchar(max)、varbinary(max))页的数目

    LOB物理读取:从磁盘读取的text、ntext、image、大值类型页的数目

    LOB预读:为进行查询而放入缓存的text、ntext、image、大值类型页的数目

    然后再来运行一遍,不清空缓存

    1 SET STATISTICS IO ON
    2 GO
    3 
    4 SELECT DISTINCT([ProductID]),[UnitPrice] FROM [dbo].[SalesOrderDetail_test]
    5 WHERE [ProductID]=777
    6 GO

    结果集返回:

    1 表 'SalesOrderDetail_test'。扫描计数 5,逻辑读取 15064 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,
    2 lob 物理读取 0 次,lob 预读 0 次。

    这次逻辑读取不变,还是15064页。但是物理读取和预读都是0了。说明数据已经缓存在内存里
    第二次运行不需要再从磁盘上读一遍,节省了时间

    为了不影响其他测试,请运行下面语句关闭SET STATISTICS IO ON

    1 SET STATISTICS IO OFF
    2 GO

    SET STATISTICS PROFILE ON


    这是三个设置中返回最复杂的一个,他返回语句的执行计划,以及语句运行在每一步的实际返回行数统计。

    通过这个结果,不仅可以得到执行计划,理解语句执行过程,分析语句调优方向,也可以判断SQLSERVER是否

    选择了一个正确的执行计划。

    1 SET STATISTICS PROFILE ON
    2 GO
    3 SELECT COUNT(b.[SalesOrderID])
    4 FROM [dbo].[SalesOrderHeader_test] a
    5 INNER JOIN [dbo].[SalesOrderDetail_test] b
    6 ON a.[SalesOrderID]=b.[SalesOrderID]
    7 WHERE a.[SalesOrderID]>43659 AND a.[SalesOrderID]<53660
    8 GO

    返回的结果集很长,下面说一下重要字段


    注意:这里是从最下面开始向上看的,也就是说从最下面开始一直执行直到得到结果集所以(行1)里的rows字段显示的值就是这个查询返回的结果集。

    而且有多少行表明SQLSERVER执行了多少个步骤,这里有6行,表明SQLSRVER执行了6个步骤!!

    Rows:执行计划的每一步返回的实际行数

    Executes:执行计划的每一步被运行了多少次

    StmtText:执行计划的具体内容。执行计划以一棵树的形式显示。每一行都是运行的一步,都会有结果集返回,也都会有自己的cost

    EstimateRows:SQLSERVER根据表格上的统计信息,预估的每一步的返回行数。在分析执行计划时,

    我们会经常将Rows和EstimateRows这两列做对比,先确认SQLSERVER预估得是否正确,以判断统计信息是否有更新

    EstimateIO:SQLSERVER根据EstimateRows和统计信息里记录的字段长度,预估的每一步会产生的I/O cost

    EstimateCPU:SQLSERVR根据EstimateRows和统计信息里记录的字段长度,以及要做的事情的复杂度,预估每一步会产生的CPU cost

    TotalSubtreeCost:SQLSERVER根据EstimateIO和EstimateCPU通过某种计算公式,计算出每一步执行计划子树的cost

    (包括这一步自己的cost和他的所有下层步骤的cost总和),下面介绍的cost说的都是这个字段值

    Warnings:SQLSERVER在运行每一步时遇到的警告,例如,某一步没有统计信息支持cost预估等。

    Parallel:执行计划的这一步是不是使用了并行的执行计划

    从上面结果可以看出执行计划分成4步,其中第一步又分成并列的两个子步骤

    步骤a1(第5行):从[SalesOrderHeader_test]表里找出所有a.[SalesOrderID]>43659 AND a.[SalesOrderID]<53660的值

    因为表在这个字段上有一个聚集索引,所以SQL可以直接使用这个索引的seek

    SQL预测返回10000条记录,实际也就返回了10000条记录.。这个预测是准确的。这一步的cost是0.202(totalsubtreecost)

    步骤a2(第6行):从[SalesOrderDetail_test]表里找出所有 a.[SalesOrderID]>43659 AND a.[SalesOrderID]<53660的值

    因为表在这个字段上有一个非聚集索引,所以SQL可以直接使用这个索引的seek

    这里能够看出SQL聪明的地方。虽然查询语句只定义了[SalesOrderHeader_test]表上有a.[SalesOrderID]>43659 AND a.[SalesOrderID]<53660过滤条件,

    但是根据语义分析,SQL知道这个条件在[SalesOrderDetail_test]上也为真。所以SQL选择先把这个条件过滤然后再做join。这样能够大大降低join的cost

    在这一步SQL预估返回50561条记录,实际返回50577条。cost是0.127,也不高

    步骤b(第4行):将a1和a2两步得到的结果集做一个join。因为SQL通过预估知道这两个结果集比较大,所以他直接选择了Hash Match的join方法。

    SQL预估这个join能返回50313行,实际返回50577行。因为SQL在两张表的[SalesOrderID]上都有统计信息,所以这里的预估非常准确

    这一步的cost等于totalsubtreecost减去他的子步骤,0.715-0.202-0.127=0.386。由于预估值非常准确,可以相信这里的cost就是实际每一步的cost

    步骤c(第3行):在join返回的结果集基础上算count(*)的值这一步比较简单,count(*)的结果总是1,所以预测值是正确的。

    其实这一步的cost是根据上一步(b)join返回的结果集大小预估出来的。我们知道步骤b的预估返回值非常准确,所以这一步的预估cost也不会有什么大问题

    这棵子树的cost是0.745,减去他的子节点cost,他自己的cost是0.745-0.715=0.03。是花费很小的一步

    步骤b(第2行):将步骤c返回的值转换为int类型,作为结果返回

    这一步是上一步的继续,更为简单。convert一个值的数据类型所要的cost几乎可以忽略不计。所以这棵子树的cost和他的子节点相等,都是0.745。

    也就是说,他自己的cost是0

    通过这样的方法,用户可以了解到语句的执行计划、SQL Server预估的准确性、cost的分布

    最后说一下:不同SQL Server版本,不同机器cost可能会不一样,例如SQL Server 2005 、SQL Server 2008

    二、转:http://blog.csdn.net/kookob/article/details/8289163

    (1)数据库的设置:如果你的数据库记录数不会超过30万条?如果你的数据库记录超过100万条?该如何设置数据库?一个或多个? 
     (2)数据库表的设置:当你的某个数据库表记录超过100万级别,而且每天大量增长,这是一个不得不考虑的问题。如果你的系统浏览量很大,即使是30万条记录也是需要考虑的。 
     (3)索引的使用:索引可以大大提高数据库访问速度。什么时候用?哪些字段使用? 
     (4)存储过程的使用:存储过程终归是比较好的,但是如果需要维护成百上千的存储过程,未必是划算的工程。 
     (5)高效的分页技术:数据库记录分页列表是大量必须使用的基本技术,怎样的分页是快速的?

     宗旨你需要从上述5个方面考虑数据库的优化。

     什么时候需要数据库优化? 
     (1)编写代码之前; 
     (2)系统速度慢了的时候;

     下面就是一些具体的优化技巧了。

    (1)超大量记录数据库的优化技巧

     如果你的数据库表记录有超过100万级别,而且不断增长中。可以采取两个手段: 
     第一:将数据库表拆分到不同的库中,比如 tblMEMBER 就可以拆分到 DB1 与 DB2 中去。 
     实际上,可以拆分到 DB001 ... DB100 甚至更多的库中间去。 
     DB1 与 DB2 最好不在一块硬盘上。 
     第二:如果更大量级的数据,则最好拆分到不同的数据库服务器中去。

     数据库的拆分带来的是查询等操作的复杂性。简单地可以通过 hash 或者 按序号 匹配不同的数据库。复杂一些,应该设置一个独立的应用服务器(软件)协调其中的操作。

    (2)中等量级数据库的优化技巧

     所谓中等量级数据库是指数据库100万-500万条记录左右(单个数据库表)。这样的数据库为了提高访问(响应)速度,可以将表拆分到更小的表。比如 tblMEMBER 可以拆分为 tblMEMBER_00 ... tblMEMBER_99 。 
     这样可以保证每个表的记录数不超过50万,那速度是"相当"快了。

    (3)避免使用视图(viewport)与关联

     视图viewport与关联都是为了程序员处理相对复杂的数据管理提供方便的手段。万物有其利,必有其弊。视图和关联提高了编程效率,都会较大地影响数据库的访问效率(事实上并不像一般资料说介绍的的那样高效),因此如果是web应用,则建议一般不要使用视图与关联。

    (4)不要忘记索引(index)也不要滥用索引(index)

     索引是提高数据库效率的简单又高效的方法。只要是设置了数据库表(table),就不要忘记设置索引(index)。将索引设置在经常用于排序的字段上,其他字段就不要设置了。 
     索引不是越多越好,也不是什么字段都适合建立索引的。数据重复性太多的字段不要设置索引。比如 tblMEMBER 的 iSex 字段只有 0 1 两个值,就不要设置索引。

    (5)二进制的 text image 等字段应该单独设置别的表中

     一般的数据库应用难免都需要保存比如描述、图片等信息;一般描述类信息用 text 字段,图片类信息用 image 字段;这里要说的是,不要将这些字段与其他字段放在一个表中。 
     比如: 
    > 纯文本方式> 打印
    tblMEMBER  
    id (int)  
    cName (varchar)(64)  
    cDescription (text)  
    bPhoto (image)  
    dDate (datetime)  
    就应该拆分为3个表  
    tblMEMBER  
    id (int)  
    cName (varchar)(64)  
    dDate (datetime)  
    tblMEMBER_DESC  
    id (int)  
    cDescription (text)  
    dDate (datetime)  
    tblMEMBER_PHOTO  
    id (int)  
    bPhoto (image)  
    dDate (datetime)  
    (6)不要使用文本类型的 id

     一般的数据库表都会以一个种子字段作为主键。可以在与不少年青的程序员朋友沟通过程中,发现他们很喜欢用字符串类型的作为系统的 id 号。 
     比如:id = XX XX XX XX 这样的字符串,每两个位置代表不同的类别等含义。 
     不知道是那本教材如此误人子弟,作出这样的表率 :< 
     作为系统的 id 号,一定要使用数字型的。

    (7)数据库表table的字段field不要太多

     本以为无需说明,也是发现不少的朋友,为了省事,一股脑把所有的相关字段都放在一个表中间。这样做的后果便是,程序写起来简单了,运行效率下来了。 
     无论字段多少,有两类字段是必须独立出去的:一是进程更新的字段,比如文章的点击次数字段iShow,二是二进制或者是text字段;

    (8)将字符串(varchar)比较变成数字型(int)比较

     每个系统都会有用户管理,其中必然有 昵称,密码,邮件等的字符串类型数据比较的问题。在数据库操作中,字符串比较的效率是相当低下的。因此遇到字符串的比较,必须将其转换为数字型比较。 
     具体做法是:在数据库表中增加相应的数字字段,比如 cNickname -> iNickNumber ,其中 iNickNumber 的数值为 cNickname 的 哈希值(如何计算字符串的哈希值?请参阅本站的其他文章)。 
     通过这样的转换,系统效率可以提高 100 倍哦!!!

    (9)为每个数据库表(table)设置 datetime 字段

     在许多情况下,很多的表是不需要 datetime 字段用于保存时间的。本文的建议是你应该为每个表都设置 datetime 字段,而且默认值为 getdate()。 
     我们的经验是,datetime 是实数,占用字节不多;在进行系统维护,远程备份等环节都会发挥意想不到的效果。

    (10)适当使用存储过程(Stored Processing)

     存储过程(sp)已经被大大地宣传了,本文也不例外地赞许采用存储过程。本文的建议是只在下列情况才使用存储过程:一是一个业务处理是事务,包含了多个处理过程;二是一种处理被高频使用,使用存储过程可以提高效率;

    (11)使用高效的分页(ination)技术

     数据库记录分页列表是大量必须使用的基本技术,因此本文建议你在每个数据库中建立下面的存储过程: 
    > 纯文本方式> 打印
    CREATE PROCEDURE xsp_ination  
    (  
    @tblName   varchar(64),                  
    @strGetFields varchar(256) = "*",   
    @fldName varchar(64)="",                  
    @PageSize   int = 20,                     
    @PageIndex  int = 1,                          
    @OrderType bit = 1,                       
    @strWhere  varchar(256) = ""      
    )  
    AS   
    BEGIN  
    declare @strSQL   varchar(1000)     
    declare @strTmp   varchar(110)       
    declare @strOrder varchar(400)     
    SET NOCOUNT ON  
    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   
    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  
            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  
    EXEC (@strSQL)  
    if @@error=0 return 1  
    SET NOCOUNT OFF  
    END  
    GO 

     使用方法是(C#): 
    > 纯文本方式> 打印
    sql = "EXEC [dbo].[xsp_ination] "tblNEWS","*","id",40," + pindex.ToString() + ",1,"iType=" + type.ToString();  
    SqlDataReader sr = ExecuteReader(sql);  
    while (sr.Read())  
    {  
       ...  
    }  
    sr.Close(); 

  • 相关阅读:
    【分享】HTML5附件拖拽上传drop & google.gears
    【分享】return false,对阻止事件默认动作的一些测试
    【记录】随笔分类汇总
    【分享】微博 @ 符号的用户名提示效果。(想@到谁?)
    【记录】File, FileReader 和 Ajax 文件上传
    【动态】简单的JS动态加载单体
    【分享】简单页面提示插件第二版表单验证很简单
    【记录】GIT 常用命令
    【分享】jQuery animate自定义动画的简单实现
    【分享】 封装js操作textarea 方法集合(兼容很好)。
  • 原文地址:https://www.cnblogs.com/jearay/p/5725924.html
Copyright © 2020-2023  润新知