作者: TechRepublic.com
Friday, March 5 2004 9:51 AM
特别说明:
在微软的SQL服务器系统中,对数据库查询功能进行适当的优化需要懂得一些基本的查询索引和性能统计方面的知识。熟悉该系统的优化工作是如何实现的将有助于提高决策的正确性。
随着你对微软的SQL服务器数据库实现的逐渐熟悉,性能优化的需求也将进一步增加。建立一个真正实现最优查询功能的数据库环境的第一步是要懂得SQL服务器系统的优化器是如何工作的。
索引
虽然对于特定的查询来说,进行查询规划和性能优化可能只需要少量的成本估算与比较,也可以没有成本估算与比较,但是大多数的查询将从实现完全优化的工作中受益。提高查询性能的最有效的方法之一就是创建一个高效率的索引。一个构架良好的索引在执行查询工作的时候可以避免出现扫描整个数据表的情况。
在创建索引的时候,SQL服务器系统将自动度量和存储那些与索引列相关的分布状态值相对应的统计信息。这些统计信息常常被优化器用来评估查询的优化策略是否合理。
有两种类型的索引:clustered索引和non-clustered索引,根据数据集合的不同,每种类型的索引都有各自独特的优点。
clustered索引要求数据表中数据按照顺序存储。因为数据已经排序,所以对于查找一定范围的索引值时clustered 索引是非常有效的。对于查找具有唯一索引值的行信息来说,这种类型的索引性能也优于其他类型的索引。
non-clustered索引和教科书中的索引非常相似,索引在一个位置而其数据值却在另外一个位置。对于一个数据值的查询搜索来说,首先搜索non-clustered的索引,找到数据值在数据表中的位置,然后直接从这个位置得到数据。non-clustered 索引对于精确匹配查询是非常有用的。
统计学
作为一种常用的规则,和大多数商业使用需求一样,索引的数量应该尽可能少,以减少与每个查询相关的处理过程。如果要分析和优化查询的性能,首先应该度量和收集数据的统计特性。
SQL服务器系统能够维护索引值的数据统计特性。如果对其进行适当的配置,对于非索引值也能够进行统计度量。
对于性能优化,数据库管理员应该懂得几个基本的统计概念,这些概念的定义如下:
|
基数:度量在数据集中可以存在多少个唯一值。
密度:度量在数据集中唯一值的个数。密度通过如下方法得到:给定键值的行数除以数据表的总行数。优化器将忽略高密度的索引。
选择率:度量对于一个特定的查询将返回查询结果的行数。选择率通过如下方法得到:查询关键字的个数除以查询得到的行数。要计算查询规划的相对成本,优化器需要一个有效的选择率来度量。
随着列中数据的变化,索引和列统计信息就变得没有用处了,这样将导致优化器在决定如何处理查询时达不到最优性能。因此,根据数据表中数据的变化,SQL服务器系统周期的自动更新这些统计信息。通过对这些数据的采样,这种统计信息的自动更新将使得成本降到最低,而且不需要对全部数据进行分析。
最佳性能
在一个复杂的数据库表中设计并指定索引是一件非常棘手的任务。幸运的是,SQL服务器系统有一个内置的调节向导来帮助你建立最优的统计和索引集合。要提高数据库的查询性能,可以通过运行向导来提供一个基于脚本的建议列表。
对于SQL服务器查询优化器如何工作这一部分懂得越多,你就会知道对于特定的情形为什么只能用向导的建议来实现。但是,对于动态系统来说,最佳的数据库性能分析部分将需要进行周期性地更新。理解查询索引性能中的每个统计度量的真正含义将有助于你在管理决策方面有一个良好的知识基础。
更新索引统计
分布页面并不是每次一个记录更新时都要进行更新.在大型数据库中,这会导致巨大的性能损失.因此,当用户初始创建一个空表时,分布页面仍是空的.它仅在发生如下情况时才被更新:
1.用户在一个已存在数据表上创建一个索引.
2.用户进行了update satatic语句
从系统管理员角度来看,用户应该创建一个工具来自动地更新分布页面.自动更新应该至少每周一次,如果数据量每天增加10%以上则应每天一次.
因为不可能每天都添加索引,用户需要使用update statistics语句更新分布页面,用以优化SQLserver.
UPDATE STATISTICS
在指定的表或索引视图中,对一个或多个统计组(集合)有关键值分发的信息进行更新。若要基于列生成统计,请参见 CREATE STATISTICS。
语法
UPDATE STATISTICS table | view
[
index
| ( statistics_name [ ,...n ] )
]
[ WITH
[
[ FULLSCAN ]
| SAMPLE number { PERCENT | ROWS } ]
| RESAMPLE
]
[ [ , ] [ ALL | COLUMNS | INDEX ]
[ [ , ] NORECOMPUTE ]
]
参数
table | view
要更新统计的表或索引视图的名称。表名和视图名必须符合标识符的规则。有关更多信息,请参见使用标识符。由于索引名在每个数据库中不唯一,所以必须指定 table 或 view。可选择指定数据库、表或视图所有者。只有在 Microsoft? SQL Server? 2000 企业版中才支持索引视图。
index
要更新统计的索引。索引名必须符合标识符的规则。如果未指定 index,则更新指定表或索引视图中的所有索引的分发统计。若要查看索引名和描述的列表,请带表名或视图名执行 sp_helpindex。
statistics_name
要更新的统计组(集合)的名称。统计名称必须符合标识符规则。有关生成统计组的更多信息,请参见 CREATE STATISTICS。
n
是表示可以指定多个 statistic_name 组的占位符。
FULLSCAN
指定应读取 table 或 view 中的所有行以收集统计。FULLSCAN 提供与 SAMPLE 100 PERCENT 相同的行为。FULLSCAN 不能与 SAMPLE 选项一起使用。
SAMPLE number { PERCENT | ROWS }
当为较大的表或视图收集统计时,指定要采样的表或索引视图的百分比或行数。number 只允许使用整数,无论它是 PERCENT 还是 ROWS。若要对较大的表或视图使用默认采样行为,请将 SAMPLE number 和 PERCENT 或 ROWS 一起使用。Microsoft SQL Server 将确保值的采样数不低于某一数目,以保证统计有用。如果 PERCENT、ROWS 或 number 选项导致要采样的行数过小,SQL Server 则自动根据表或视图中的现有行数改正采样。
说明 默认行为是在目标表或索引视图上进行采样扫描。SQL Server 自动计算所需的样本大小。
RESAMPLE
指定使用从所有现有统计(包括索引)继承的采样速率来收集统计。如果采样速率导致要采样的行过少,SQL Server 则自动根据表或视图中的现有行数改正采样。
ALL | COLUMNS | INDEX
指定 UPDATE STATISTICS 语句是否影响列统计、索引统计或所有现有统计。如果未指定选项,则 UPDATE STATISTICS 语句影响所有的统计。每个 UPDATE STATISTICS 语句只能指定一种类型(ALL、COLUMNS 或 INDEX)。
NORECOMPUTE
指定过期统计不自动重新计算。统计过期与否取决于在索引列上进行的 INSERT、UPDATE 和 DELETE 操作的数量。指定该选项时,将导致 SQL Server 禁用自动统计重建功能。若要还原自动统计重新计算,请重新执行 UPDATE STATISTICS(不要 NORECOMPUTE 选项),或者执行 sp_autostats。
重要 禁用自动统计重新计算会导致 SQL Server 查询优化器对于涉及指定表的查询选择非最佳的策略。
注释
SQL Server 保留每个索引中关于键值分发的统计,并且使用这些统计来决定查询处理中使用哪个(或哪些)索引。用户可以通过使用 CREATE STATISTICS 语句生成基于非索引列的统计。查询优化依赖于分发步骤的准确性:
如果索引中的键值有显著变化,请对此索引重新运行 UPDATE STATISTICS。
如果索引列中添加、更改或删除大量数据(即如果键值分发更改),或者用 TRUNCATE TABLE 语句将表截断然后重新填充,请使用 UPDATE STATISTICS。
若要查看统计最近一次更新的时间,请使用 STATS_DATE 函数。
只有当能够在计算列上创建索引时,才可以在包含这些计算列的表上创建或更新统计。有关在计算列上创建索引的要求和限制的更多信息,请参见 CREATE INDEX。
权限
UPDATE STATISTICS 权限默认授予表或视图的所有者,并且该权限不可转让。
示例
A. 更新单个表的所有统计
本示例更新表 authors 上的所有索引分发统计。
UPDATE STATISTICS authors
B. 仅更新单一索引的统计
本示例仅更新表 authors 的索引 au_id_ind 的分发信息。
UPDATE STATISTICS authors au_id_ind
C. 使用 50% 采样更新特定统计组(集合)的统计
本示例首先创建表 authors 中 au_lname 列和 au_fname 列的统计组,然后对其进行更新。
CREATE STATISTICS anames
ON authors (au_lname, au_fname)
WITH SAMPLE 50 PERCENT
GO
-- Time passes. The UPDATE STATISTICS statement is then executed.
UPDATE STATISTICS authors(anames)
WITH SAMPLE 50 PERCENT
GO
D. 使用 FULLSCAN 和 NORECOMPUTE 更新特定统计组(集合)的统计
本示例更新表 authors 中的 anames 统计组(集合),强制对表 authors 中的所有行进行完全扫描,并且关闭该统计组(集合)的自动统计更新。
UPDATE STATISTICS authors(anames)
WITH FULLSCAN, NORECOMPUTE
sp_updatestats对当前数据库中所有用户定义的表运行 UPDATE STATISTICS。
语法
sp_updatestats [[@resample =] ''resample'']
返回代码值
0(成功)或 1(失败)
参数
[@resample =] ''resample''
指定 sp_updatestats 将使用 UPDATE STATISTICS 命令的 RESAMPLE 选项。新统计表将继承旧统计表的采样比率。如果未指定 ''resample'',则 sp_updatestats 使用默认采样更新统计表。该参数的数据类型为 varchar(8),默认值为 ''NO''。
注释
sp_updatestats 会显示表示其进度的消息。完成更新之后,该存储过程将报告已为所有的表更新了统计信息。
权限
只有 DBO 和 sysadmin 固定服务器角色的成员才能执行该过程。
示例
下例为数据库 pubs 中的表更新统计信息。
USE pubs
EXEC sp_updatestats
Sqlserver7 编程技术内幕提供的方法.
drop proc pr_updateindex
create proc pr_updateindex
as
set nocount on
declare get_index_curs cursor
for select name--tablename
from sysobjects --systemtable
where type=''u'' -usertable
declare @holdtable varchar(30)
declare @message varchar(40)
declare @dynamic varchar(51)
open getindex_curs
fetch next from getindex_curs into @holdtable
while @@fetch_status=0
begin
select @dynamic=''update statistics ''+@holdtable
select @message=''updating''+@holdtable
exec(@dynamic)
print @message
fetch next from getindex_curs into @holdtable
end
close getindex_curs
Copyright (C) 2003 Cameron Michelis copying and redistribution of this file is permitted provided
this notice and the above comments are preserved.
*/
Set quoted_identifier off
use master
DECLARE @fillfactor varchar(2)
DECLARE @tablename varchar(30)
DECLARE @tablename_header varchar(75)
DECLARE @dataname varchar(30)
DECLARE @dataname_header varchar(75)
DECLARE datanames_cursor CURSOR FOR SELECT name FROM sysdatabases
WHERE name not in ('master', 'pubs', 'tempdb', 'model', 'northwind')
/* Variable Initialization */
select @fillfactor = "0" -- Set Fill factor here
-- Note "0" will use original fillfactor.
/* End Variable Initialization */
OPEN datanames_cursor
FETCH NEXT FROM datanames_cursor INTO @dataname
WHILE (@@fetch_status <> -1)
BEGIN
IF (@@fetch_status = -2)
BEGIN
FETCH NEXT FROM datanames_cursor INTO @dataname
CONTINUE
END
SELECT @dataname_header = "Database " + RTRIM(UPPER(@dataname))
PRINT " "
PRINT @dataname_header
PRINT " "
EXEC ("USE " + @dataname + " DECLARE tnames_cursor CURSOR FOR SELECT name from sysobjects where type = 'U'")
Select @dataname_header = RTRIM(UPPER(@dataname))
Exec ("Use " + @dataname)
OPEN tnames_cursor
FETCH NEXT FROM tnames_cursor INTO @tablename
WHILE (@@fetch_status <> -1)
BEGIN
IF (@@fetch_status = -2)
BEGIN
FETCH NEXT FROM tnames_cursor INTO @tablename
CONTINUE
END
SELECT @tablename_header = " Updating " + RTRIM(UPPER(@tablename))
PRINT ""
PRINT @tablename_header
EXEC ("USE " + @dataname + " DBCC DBREINDEX (" + @tablename + "," + "''" + "," + @fillfactor + ")")
EXEC ("USE " + @dataname + " UPDATE STATISTICS " + @tablename)
FETCH NEXT FROM tnames_cursor INTO @tablename
END
DEALLOCATE tnames_cursor
FETCH NEXT FROM datanames_cursor INTO @dataname
END
DEALLOCATE datanames_cursor
PRINT ""
PRINT " "
PRINT "Indexing complete for All User Databases"
SET QUOTED_IDENTIFIER OFF
/* Start with master DB */
USE master
/* Create Variables */
DECLARE @DBName CHAR(64)
DECLARE @TableName CHAR(64)
DECLARE @FQTableName CHAR(64)
DECLARE @TempVar CHAR(256)
/* Create DB List */
DECLARE DBCursor CURSOR FOR
SELECT name
FROM master..sysdatabases
OPEN DBCursor
FETCH NEXT
FROM DBCursor
INTO @DBName
/* Create Database Loop */
WHILE @@FETCH_STATUS = 0
BEGIN
/* Retrieve Table List */
PRINT 'Retrieving Table List for DB ' + @DBName
EXEC ('SELECT name AS TableName INTO ##TableNames FROM [' + @DBName + ']..sysobjects WHERE type = ''U''')
/* Open Table List */
DECLARE TableCursor CURSOR FOR
SELECT TableName
FROM ##TableNames
OPEN TableCursor
FETCH NEXT
FROM TableCursor
INTO @TableName
/* Create Table Loop */
WHILE @@FETCH_STATUS = 0
BEGIN
/* Add DB Name to Table Name */
SELECT @FQTableName = QUOTENAME(RTRIM(@DBName)) + '..' + QUOTENAME(RTRIM(@TableName))
SELECT @TableName = RTRIM(@DBName) + '..' + RTRIM(@TableName)
/* ReIndex Table */
PRINT 'ReIndexing Table ' + @TableName
DBCC DBREINDEX(@TableName)
/* Update Statics on Table */
PRINT 'Updating Statistics on Table ' + @TableName
EXEC ('UPDATE STATISTICS ' + @FQTableName)
/* Get Next Table Name */
FETCH NEXT
FROM TableCursor
INTO @TableName
END
/* Close Table Cursor */
CLOSE TableCursor
DEALLOCATE TableCursor
/* Remove Tempory Table */
DROP TABLE ##TableNames
/* Preform DB Checks */
PRINT 'Preforming DB Checks on ' + @DBName
DBCC CHECKDB (@DBName)
/* Get Next Table Name */
FETCH NEXT
FROM DBCursor
INTO @DBName
END
/* Close DB Curosor */
CLOSE DBCursor
DEALLOCATE DBCursor
/* Finished */