SQL Server调优系列进阶篇(查询语句运行几个指标值监测)

标签: General | 发表时间:2014-12-31 08:00 | 作者:ajaxj
出处:http://www.geek521.com

前言

上一篇我们分析了查询优化器的工作方式,其中包括:查询优化器的详细运行步骤、筛选条件分析、索引项优化等信息。

本篇我们分析在我们运行的过程中几个关键指标值的检测。

通过这些指标值来分析语句的运行问题,并且分析其优化方式。

通过本篇我们可以学习到调优中经常利用的几个利器!

废话少说,开始本篇的正题。

技术准备

数据库版本为SQL Server2008R2,利用微软的一个更简洁的案例库(Northwind)进行分析。

利器一、IO统计

通过这个IO统计能为我们分析出当前查询语句所要扫描的数据页的数量。这里面有几个重要的概念,我们依次分析。

方法很简单,一行代码搞定:

SET STATISTICS IO ON

来看个例子

SET STATISTICS IO ON
GO
SELECT * FROM Person.Contact

这里可以看到这个语句对于数据表的操作次数,基于数据页的扫描项。

所谓的数据页就是数据库的底层数据存储方式,SQL Server以数据页的形式存储表行数据。每个数据页为8K,

8K=8192字节-96字节(页头)-36字节(行偏移)=8060字节

也就说一个数据页存储的纯数据内容为8060字节。

我们依次来解释上面出现几个读取的概念:

逻辑读

表示处理查询所需要访问页的总数。也就是说要完成一个查询语句需要读取的数据页的总数。

这里的数据页有可能来自内存,也有可能来自硬盘读取。

物理读

这个就是说来自硬盘读取的数据页数。我们知道SQL Server每次都会将读取的数据页尽可能存在于内存中,以方便下一次直接读取,提升读取速度。

所以在这里关于存储于内存中的数据页下次访问的概率,提出了一个指标:缓存命中率

缓存命中率=(逻辑读—物理读)/逻辑读

提出这个指标的提出其实就是为了衡量内存中缓存的数据页的有效性。比如:假如缓存与内存中的数据页就使用一次就不使用了,对于这种就应该及时从内存中清除掉,毕竟对于内存资源来说是非常昂贵的。应该用它来缓存命中率高的数据页。

预读

预读其实就是SQL语句在优化的时候预先读取到内存中的数据页数。这个预先读取的数据页是提前评估出来的,也就是上一篇我们文章中介绍的查询优化器要做的事情。

当然,这些预读的数据页有时候不是所有的都要用到,但是它基本能涵盖到查询用到的数据页。

这里要提示一下,预读数据是通过另外一个线程进行读取的和语句优化线程非用同一线程,并行运行,目的是快速获取数据,提升查询获取的速度。

从这个指标我们可以分析出很多问题,来举个例子:

我们新添加一张测试表,脚本如下

--执行下面脚本新生成一张表
SELECT * 
INTO NewOrders
FROM Orders
GO
--新增加一列
ALTER TABLE NewOrders
ADD Full_Details CHAR(2000) NOT NULL DEFAULT ’full details’
GO

然后利用如下脚本来看下这张表的大小

EXEC sp_spaceused NewOrders,TRUE
GO

我们可以看到这张表数据页的总大小为2216KB,我们知道一页为8KB,可以推断出这个表的数据页有:

2216(数据页总大小)/8(一个数据页大小)=277页

也就是说这个数据表有277个数据页。

当然,我们也可以通过如下DMV视图来查看该页的数据页数

SELECT * 
FROM SYS.dm_db_index_physical_stats
(DB_ID(’Northwind’),object_id(’NewOrders’),NULL,NULL,’detailed’)

经过上面的分析,

我们可以推测,在查询这张表做全表扫描的时候,理论的数据页的逻辑读数就应该为277次

通过如下语句验证下

--先清空缓存数据,生产机慎用
DBCC DROPCLEANBUFFERS

SET STATISTICS IO ON

SELECT * FROM NewOrders

我去…

这里的逻辑读取为1047页,和我们上面的推断277页不相符…擦…神马原因!!!

这里就是我们要分析的数据页Forwarded record现象造成的。因为我们在新建立的表,在后面新添加的一列数据:Full_Details,类型为CHAR(2000)的数据列,当数据行中的变长列增长使得原有页无法容纳下数据行时,数据将会移动到新的页中,并在原位置留下一个指向新页的指针,这就是所谓的: Forwarded record

我们可以通过如下DMV视图,查看该表的Forwarded Record形成的页有多少

SELECT * 
FROM SYS.dm_db_index_physical_stats
(DB_ID(N’Northwind’),object_id(’NewOrders’),NULL,NULL,’detailed’)

纠正一下:上图的770数据页为Forwarded Record页,非拆分页的概念(感谢院友 wy123 指出)。

看到了,这里的Forwarded Record页为770页,那么我们就可以推测出我们的逻辑读数量来了

277(原数据页)+770(Forwarded Record页)=1047页

所以上面的我们的问题就分析出原因了。

我们通过此表也展示了一个Forwarded Record页的问题:会影响查询性能。

解决的方式很多种,最简单的方式就是重建聚集索引。

CREATE CLUSTERED INDEX orderID_C ON NewOrders(OrderID)
GO
DROP INDEX NewOrders.orderID_C
GO
SET STATISTICS IO ON
SELECT * FROM NewOrders
GO

通过IO统计项,除了可以分析出上面的Forwarded Record页造成的碎片外,更重要的地方使用来对比不同查询语句之间的读取次数,通过降低读取的次数来优化语句。

关于预读的情况,我们在前面已经分析了,其数据时通过另外一个线程在T-SQL查询语句优化的时候进行数据的预加载。

所以这个线程在预读数据的时候其实是有一个参考值的,根据这个参考值读取出来的数据才能保证大部分数据是有用的,也就是提高上面提到的缓存命中率。

关于这个参考值,我分析了下,其实是分为两中情况分析的。

首先、如果是数据表为堆表,SQL Server获取的方式只能通过全表扫描了。而此方式为了避免重复读取,增加消耗,所以一次的预读并非读取一个数据页,

而是一段物理上的连续64个页

来看联机丛书的官方解释:

预读机制允许数据库引擎从一个文件中读取最多 64 个连续页 (512KB)。该读取作为缓冲区高速缓存中相应数量(可能是非相邻的)缓冲区的一次散播-聚集读取来执行。如果此范围内的任何页在缓冲区高速缓存中已存在,当读取完成时,所读取的相应页将被放弃。如果相应页在缓存中已存在,也可以从任何一端“裁剪”页的范围。

所以,如果我们的表在物理上不是连续页,那么读取次数就不好怎么确定了。

我们来看个堆表的例子

SET STATISTICS IO ON
--新建个测试表
SELECT * INTO NewOrders_TEST FROM NewOrders
SELECT * FROM NewOrders_TEST

这里预读的次数为8次,所以我估计底层的数据页肯定不是连续的。所以造成了多出了3次。

我们可以DBCC IND()进行查询下,来验证下我的推断。

DBCC IND(’Northwind’,’NewOrders_TEST’,1)

数据信息比较多,我将其粘贴到Excel中,然后做了一个折线图,其中涂掉的部分其实是没有数据页的,所以不会产生一次读取。

关于读取顺序标示的也有点问题,不过确定的总数肯定是8次…..

希望这种方式,各位看官能看懂了…希望我也表述明白了。

其次、如果表非堆表,也就是说存在聚集索引项,那么好了,SQL Server很轻松的找到了它预读的参考依据:统计信息。

并且,我们知道数据以B-Tree数存储,读取的数据页都存在与叶子节点。所以基本没有了什么连续读取的感念。

一个叶子节点就是一个数据页,一个数据页就是一次预读。

来看个例子:

我们将上面的表添加上聚集索引项,再一次清空缓存,执行查询,脚本如下

CREATE CLUSTERED INDEX NewOrders_TESTIndex ON NewOrders_TEST(OrderID)
GO
SELECT * FROM NewOrders_TEST

这里添加了聚集索引,SQL Server仿佛一下看到了救星,根据统计信息,预读数据就可以。

所以如果统计信息有错误,就造成了预读的乱读取….然后严重降低了缓存命中率…..然后严重增加了内存中换出换入的速度….增加了CPU….

好了,咱们继续文章,上面我们提到的这个预读数据行,可以在如下DMV中查到。

SELECT * 
FROM SYS.dm_db_index_physical_stats
(DB_ID(N’Northwind’),object_id(’NewOrders_TEST’),NULL,NULL,’detailed’)

从这个DMV视图中可以看到这种表统计信息为277个数据页,所以形成了277次预读。

但是,事实这个数据表是279页,也就是说统计的信息有问题,造成了少读读取了2个数据页,而为了弥补这个统计过失就出现了2次物理读,重新从硬盘中获取。

利器二、时间统计

关于时间统计这个很简单,就是统计T-SQL执行语句执行时间项,包括CPU占用时间、语句编译时间、语句执行总时间等项。

使用方法也很简单,一行代码

SET STATISTICS TIME ON

通过这个参数,可以分析出以上信息,其作用主要是用来对比查询语句调优中的执行时间,我们的目标就是降低执行时间。

举例:我们通过开启时间统计,来对比下,上面的查询语句,在第一次运行和以后运行(数据已经缓存)的时间对比,了解下缓存的重要性

再次执行的时间

缓存追踪(补充于2014年12月25日)

当然我们也可以再深入一点,如果想查看该部分数据在内存中缓存的明细,可以通过如下DMV脚本查看

SELECT * FROM sys.dm_os_buffer_descriptors
WHERE DB_NAME(database_id)=’Northwind’
AND page_type=’DATA_PAGE’
ORDER BY page_id

也可以通过该DMV分析出各个库在内存中占据的大小比例,脚本如下:

--清除缓存
dbcc dropcleanbuffers
--查看缓存内容中在内存大小
SELECT COUNT(*)*8/1024 as ’Cached Size(MB)’
       ,CASE database_id 
        WHEN 32767 THEN ’ResourceDB’
        ELSE DB_NAME(database_id)
        END AS ’Database’
FROM sys.dm_os_buffer_descriptors
GROUP BY DB_NAME(database_id),database_id
ORDER BY ’Cached Size(MB)’ DESC

经过这次查询,这张表已经全部缓存到内存里了,因为整张表总共就2MB的大小

文章已经有点长度了…先到此吧。

关于调优内容太广泛,我们放在以后的篇幅中介绍,有兴趣的可以提前关注。

参考文献

  • 微软联机丛书 读取页
  • 参照书籍《SQL.Server.2005.技术内幕》系列

系列目录:

相关 [sql server 系列] 推荐:

SQL Server--索引

- - CSDN博客推荐文章
         1,概念:  数据库索引是对数据表中一个或多个列的值进行排序的结构,就像一本书的目录一样,索引提供了在行中快速查询特定行的能力..             2.1优点:  1,大大加快搜索数据的速度,这是引入索引的主要原因..                             2,创建唯一性索引,保证数据库表中每一行数据的唯一性..

SQL Server 面试

- - SQL - 编程语言 - ITeye博客
在SQL语言中,一个SELECT…FROM…WHERE语句称为一个查询块,将一个查询块嵌套在另一个查询块的WHERE子句中的查询称为子查询. 子查询分为嵌套子查询和相关子查询两种. 嵌套子查询的求解方法是由里向外处理,即每个子查询在其上一级查询处理之前求解,子查询的结果作为其父查询的查询条件. 子查询只执行一次,且可以单独执行;.

SQL Server调优系列基础篇(并行运算总结)

- - 极客521 | 极客521
上三篇文章我们介绍了查看查询计划的方式,以及一些常用的连接运算符、联合运算符的优化技巧. 本篇我们分析SQL Server的并行运算,作为多核计算机盛行的今天,SQL Server也会适时调整自己的查询计划,来适应硬件资源的扩展,充分利用硬件资源,最大限度的提高性能. 闲言少叙,直接进入本篇的正题.

SQL Server调优系列基础篇(常用运算符总结)

- - 极客521 | 极客521
上一篇我们介绍了如何查看查询计划,本篇将介绍在我们查看的查询计划时的分析技巧,以及几种我们常用的运算符优化技巧,同样侧重基础知识的掌握. 通过本篇可以了解我们平常所写的T-SQL语句,在SQL Server数据库系统中是如何分解执行的,数据结果如何通过各个运算符组织形成的. 基于SQL Server2008R2版本,利用微软的一个更简洁的案例库(Northwind)进行解析.

SQL Server优化50法

- - CSDN博客推荐文章
虽然查询速度慢的原因很多,但是如果通过一定的优化,也可以使查询问题得到一定程度的解决.   查询速度慢的原因很多,常见如下几种:没有索引或者没有用到索引(这是查询慢最常见的问题,是程序设计的缺陷).   I/O吞吐量小,形成了瓶颈效应.   没有创建计算列导致查询不优化.   内存不足网络速度慢查询出的数据量过大(可以采用多次查询,其他的方法降低数据量).

SQL Server 中的事务

- - CSDN博客推荐文章
       事务要有非常明确的开始和结束点,SQL Server 中的每一条数据操作语句,例如SELECT、INSERT、UPDATE和DELETE都是隐式事务的一部分. 即使只有一条语句,系统也会把这条语句当做一个事务,要么执行所有的语句,要么什么都不执行.         事务开始之后,事务所有的操作都会写到事务日志中,写到日志中的事务,一般有两种:一是针对数据的操作,例如插入、修改和删除,这些操作的对象是大量的数据;另一种是针对任务的操作,例如创建索引.

SQL Server优化50法

- - CSDN博客数据库推荐文章
  虽然查询速度慢的原因很多,但是如果通过一定的优化,也可以使查询问题得到一定程度的解决.   查询速度慢的原因很多,常见如下几种:. 没有索引或者没有用到索引(这是查询慢最常见的问题,是程序设计的缺陷). I/O吞吐量小,形成了瓶颈效应. 查询出的数据量过大(可以采用多次查询,其他的方法降低数据量).

SQL Server 查询步骤 - pursuer.chen

- - 博客园_首页
标签:SQL SERVER/MSSQL SERVER/数据库/DBA/查询步骤.       查询步骤是很基础也挺重要的一部分,但是我还是在周围发现有些人虽然会语法,但是对于其中的步骤不是很清楚,这里就来分解一下其中的步骤,在技术内幕系列里面都会有讲到.  TOP于ORDER BY的关系. INSERT INTO Customers VALUES(1,'深圳'),(2,'广州'),(3,'武汉'),(4,'上海'),(5,'北京').

SQL Server调优系列进阶篇(查询语句运行几个指标值监测)

- - 极客521 | 极客521
上一篇我们分析了查询优化器的工作方式,其中包括:查询优化器的详细运行步骤、筛选条件分析、索引项优化等信息. 本篇我们分析在我们运行的过程中几个关键指标值的检测. 通过这些指标值来分析语句的运行问题,并且分析其优化方式. 通过本篇我们可以学习到调优中经常利用的几个利器. 废话少说,开始本篇的正题. 数据库版本为SQL Server2008R2,利用微软的一个更简洁的案例库(Northwind)进行分析.

sql server复灾 你懂了吗?

- brett80 - 博客园-首页原创精华区
很多时候我们不小心错误delete了一下,或者update一下怎么办,或者直接把数据库删除了,怎么办呢,是不是就一定没有办法呢. 下面让我来教大家我现学现卖的两招. 做之前我们要设置数据库恢复模式:. 首先我们创建一个表:插入几条数据. 我们现在有五条数据了,我们对数据做一个备份. 做任何差异备份,和日志之前,一定要做一个完整备份.