SQLServer2000里自动重建索引
/ s+ [" W4 ^' r2 s6 ^, T YSQLServer2000里自动重建索引
$ g; } M5 O& ?9 O--王成辉翻译整理,转贴请注明出自微软BI开拓者www.windbi.com ( ]) i3 H$ w/ n7 ~. h1 n
--原帖地址 / u3 P& Z5 U2 |' l" V
在所有的OLTP环境里,实质上所有的索引都将随着时间产生碎片。几乎所有的UPDATE、INSERT、DELETE活动都将引起索引比最初创建时变得更无组织。页拆分更多,大量的页上只有很少的数据,因此满足每个SELECT需要更多的I/O。数据和索引的碎片越多,应用程序就会更慢,数据花费的空间就更多。对此你能做什么呢?你可以定期的重建索引。 ' j3 I3 Q1 d: N5 [' v' A# ^
那么什么可以立即使用呢? $ I7 w. d; E/ `- j8 U9 C! ~
基本上你可以使用数据库维护向导来执行索引重建,创建维护计划来完成。如果你原意接受它固有的缺陷,这也可以使用。首先,用维护向导来配置和完成索引重建是不慎重的。它将重建每一个索引,不管它是否需要重建。如果你有一个有很多大表和大量索引的大数据库,这会出问题,因为不加区别的重建整个数据库的索引会花费很长的时间,会使你的维护窗口不可用。问题在于,要么全部重建,要么全部不重建,你根本不能以任何方式分批处理数据库的表。 * d7 f2 K5 C% V' j" j
0 `3 t. f- S1 y% K+ L 2 w3 O8 G: |5 r6 v7 D
那么有什么别的能做吗?你可以写一个脚本来重建选择的表的索引。这样你能对数据库分批处理以减少在重建索引时你维护窗口执行的时间。你需要将这个时间减小到最少,因为重建索引会对表执行排它锁,在重建索引期间禁止用户访问。所以你可以每周的每个工作日的晚上重建五分之一表的索引,所有的索引至少一周做一次。然而,这也是不慎重的――你将重建所有表的索引而不论数据和索引是否是有碎片。
8 j! O, j3 m, m* x- T& g$ J6 o这里推荐选择性的重建索引。你需要检查表的索引和数据的碎片,保留数据,据此操作,重建索引要用确定的且区别对待的方式。仅仅通过这样系统的方法,你可以仅重建那些实际需要重建的表的数据和索引。而且也只有这种方式能最小化索引重建的时间。在整个索引重建期间,如果你不想影响你的用户的话,减少索引重建的时间是至关重要的。
0 _1 S2 C Y) T: q那么我们怎样可以解决呢?
# o" P8 b' \! e可以使用命令
" \# c7 J6 G% ?1 I* x: w7 [DBCC SHOWCONTIG() $ q7 K& E4 P+ w: V; v
SQLServer2000比以前版本有一个大的改进就是这个简单而又至关重要的命令。DBCC SHOWCONTIG是SQLServer提供来检查索引碎片情况的工具。在以前的版本里(7.0和更早的版本),这个命令只输出文本,如果手工处理这个命令很好,然而,要实现自动化目的,它会带来严重的问题。那意味着你要循环执行每一个表并将结果输出到文本文件,然后为了读和解释原文的输出结果以便获得你寻找的信息,需要进行烦人的结构化处理。 9 ^! d/ v- b( i
SQLServer2000对DBCC SHOWCONTIG()命令引进了一个关键子句,名为WITH TABLERESULTS。这意味着你能运行这个命令然后将捕获的数据直接输出到表里,而不是还需要使用XP_CMDSHELL来操作的文本文件里。 0 d) Q0 a8 A( l. @! @
在SQLServer2000里,这意味着你能结构化的循环处理表,通过在它们上面运行DBCC SHOWCONTIG命令以将捕获碎片信息插入表中。然后你能循环使用这个结果,根据碎片的情况,选择性的进行碎片整理。可以用下面的存储过程实现:
' U9 m8 `& h h8 h/ X5 H! OCREATE PROCEDURE sp_defragment_indexes @maxfrag DECIMAL
' @0 e3 z7 X u3 i; ~. iAS --王成辉翻译整理,转贴请注明出处
5 |- u7 a4 `( K- @$ X--声明变量
# L! P( ?/ d0 d1 G# w3 y) Q" zSET NOCOUNT ON
6 j/ t: ~5 `- l t$ x2 r5 P; n. Z* zDECLARE @tablename VARCHAR (128) 0 x* Z' u. q. C+ Q
DECLARE @execstr VARCHAR (255)
- B# Q8 R h5 Z4 G1 e0 w" D( D- `) Q% JDECLARE @objectid INT - d1 n1 x- u- G/ H# i
DECLARE @objectowner VARCHAR(255) % ]. D" j7 u4 A- q: U9 j3 j0 l( _
DECLARE @indexid INT
6 S$ C; C: J9 x: I8 X, f, z. IDECLARE @frag DECIMAL
5 |6 a3 Y0 u, m9 M" Z! U. hDECLARE @indexname CHAR(255)
2 _6 t9 V1 G' H/ t* p/ hDECLARE @dbname sysname
1 _$ ?. a, W/ I, r5 e9 J. bDECLARE @tableid INT
! f% y0 g4 {& J0 D& QDECLARE @tableidchar VARCHAR(255) / w; @0 l' z" o, F$ o' I
--检查是否在用户数据库里运行 t8 d6 G6 F* W* f3 ~* `( c( o; p8 q, @
SELECT @dbname = db_name()
3 a- ~. E5 Z% q! K% m; I1 IIF @dbname IN ('master', 'msdb', 'model', 'tempdb') 2 o2 B6 S+ H: u) }- _$ h
BEGIN
. u7 k* M0 w. S( A1 cPRINT 'This procedure should not be run in system databases.' ( T; `: p9 l5 O7 G" d" [
RETURN 6 V4 x; d0 w* _, \3 ?3 a4 J
END
, j' n- r' H# @ |0 y( ~1 `. O--第1阶段:检测碎片
( S8 _) H* S. i {2 k2 C1 e# _--声明游标
2 d4 y, U# X/ ]' y( B& O' z5 O( JDECLARE tables CURSOR FOR ) A5 q4 {7 o+ `: R/ | x: o5 S3 }$ a' `
SELECT convert(varchar,so.id)
5 x: @0 t' D @* h. nFROM sysobjects so 3 Z' C: j8 @, V1 a
JOIN sysindexes si i/ z8 m( Z8 y
ON so.id = si.id
! V3 D6 Y/ x' o- N" ~' m w. {WHERE so.type ='U'
# O- W T7 P0 w0 j `: H$ f0 mAND si.indid < 2
: ^0 e. M$ ?& U6 X0 v2 ]AND si.rows > 0
j4 T% k* c: f& l; Y-- 创建一个临时表来存储碎片信息
' _+ ?! t2 z3 [ |3 c0 |CREATE TABLE #fraglist (
( I/ Q/ ~9 I6 B: N' W3 ^ObjectName CHAR (255), 1 [# @3 ]& e( ]2 p5 X5 t
ObjectId INT,
7 _; H% h; T+ Q2 w' ]( G1 }IndexName CHAR (255), ; u& q. }& v/ t$ s
IndexId INT, ' r7 y: I/ f6 `% A% d9 Z
Lvl INT,
, M2 Q3 A" M3 l8 g; ~8 LCountPages INT,
9 L* J1 U2 l% }6 ?CountRows INT,
# t3 J) J# ~0 {! |- G/ HMinRecSize INT,
; d7 `4 T* e0 J* Z0 hMaxRecSize INT,
8 Q5 Y$ P8 s. o5 yAvgRecSize INT,
6 |' m8 L4 B* ~0 l7 \# E1 E# LForRecCount INT, 1 L. i+ w. H+ b
Extents INT, . H! `3 H' R+ }3 S
ExtentSwitches INT, , l* S. t- Q$ q: y; {1 A
AvgFreeBytes INT, . ]1 M) D I) m/ S1 K' C% ~
AvgPageDensity INT, ; g( V1 n- `) ?* w1 `
ScanDensity DECIMAL,
. v- Y9 o. |* j) O3 N" J+ VBestCount INT, 9 A8 C4 D! H( N
ActualCount INT,
, t% V5 i$ v% S' lLogicalFrag DECIMAL,
/ n* F+ T" s9 k; M: X2 d1 iExtentFrag DECIMAL)
7 B8 J0 \2 w6 V, @- ~! k--打开游标
# R' y; |0 w( C( A6 NOPEN tables 7 [5 q( V- t$ p. i' G0 p2 R0 U
-- 对数据库的所有表循环执行dbcc showcontig命令 6 h$ c ?6 {# e( b2 K1 P- K4 L
FETCH NEXT
! q0 d; d( G0 ~5 Y1 ~6 S6 t; \FROM tables
& l. T: v; e6 M: P( a) {2 qINTO @tableidchar ! M1 x5 ]# f9 Q2 c6 `6 Y
WHILE @@FETCH_STATUS = 0 ; K" D! G% n; U+ n y4 |
BEGIN / i! T: J( Y$ _) a
--对表的所有索引进行统计 + y$ S0 Y. ~% g3 K( B3 X
INSERT INTO #fraglist
' f, }# l) g0 U1 i3 |5 HEXEC ('DBCC SHOWCONTIG (' + @tableidchar + ') WITH FAST, TABLERESULTS, ALL_INDEXES, NO_INFOMSGS') % T1 ]2 j) V+ \5 Z; G% A, C' R
FETCH NEXT + t/ W3 T+ d# V6 C& T( G( \- P
FROM tables ( ? c, @( [9 N: f1 g
INTO @tableidchar : `5 K! V) M2 B- [: \4 ?' j6 R: C
END 9 N) x$ H- U4 n6 f/ `: @8 ], U8 r
-- 关闭释放游标 ( W k" G7 s \/ i0 U0 V& ]9 o
CLOSE tables $ s5 w6 j' ]( L" E+ C
DEALLOCATE tables 4 v; i" i$ @, V, i+ w: J( {
-- 为了检查,报告统计结果 ) A4 `/ G7 J2 B9 s$ T
SELECT * FROM #fraglist ) c, L- [" n; U
--第2阶段: (整理碎片) 为每一个要整理碎片的索引声明游标 " r/ W; X) `2 y' }* Z5 f
DECLARE indexes CURSOR FOR 7 |5 Y$ C& _( b3 {4 G a
SELECT ObjectName, ObjectOwner = user_name(so.uid), ObjectId, IndexName, ScanDensity W+ @. V' _# n% Q
FROM #fraglist f 5 o: a/ `6 _3 r! y
JOIN sysobjects so ON f.ObjectId=so.id + K {7 D7 P. R6 [- U
WHERE ScanDensity <= @maxfrag
5 G6 s6 r, Z/ w6 x" l+ ~- r1 MAND INDEXPROPERTY (ObjectId, IndexName, 'IndexDepth') > 0
% _- C2 f0 D" v5 \7 @/ d' r-- 输出开始时间 $ U8 j" P# v' J3 |. A5 K' v# O
SELECT 'Started defragmenting indexes at ' + CONVERT(VARCHAR,GETDATE())
' U+ m% v3 a& V) e# u: U0 T2 t2 t; C--打开游标 1 `2 i8 k s" F$ o, O8 d7 A
OPEN indexes 5 v8 `' z: p- Z- h9 ]+ B# o6 G4 }
--循环所有的索引
# W0 N) r0 I/ X- T! G. N" h% JFETCH NEXT
* q/ G; m4 h* k( j7 `FROM indexes
" r, n$ z# I3 b p# o) G; w% F' \INTO @tablename, @objectowner, @objectid, @indexname, @frag * E& P$ H% P6 m2 }7 ]
WHILE @@FETCH_STATUS = 0
! }. C/ u7 x# X0 [: W* E: }5 Z4 lBEGIN 0 ?+ q' l# @: [: C5 K* [+ z/ E
SET QUOTED_IDENTIFIER ON
8 P6 m ~" l5 G5 wSELECT @execstr = 'DBCC DBREINDEX (' + '''' +RTRIM(@objectowner) + '.' + RTRIM(@tablename) + '''' +
+ a+ G2 a4 Z3 ~6 C( B', ' + RTRIM(@indexname) + ') WITH NO_INFOMSGS' " l% s/ ^8 N0 T4 Z; z: v
SELECT 'Now executing: '
; y3 |% t, L8 N+ b! H0 C1 ZSELECT(@execstr)
& ?4 p9 ~7 n+ t8 [+ y9 Y! b0 IEXEC (@execstr) * |! y" J' Q+ ^
SET QUOTED_IDENTIFIER OFF
4 n3 }5 h4 d0 j: \. |FETCH NEXT 6 E* ^# }$ f8 A/ Z* u
FROM indexes 3 ]0 l) @2 x. j
INTO @tablename, @objectowner, @objectid, @indexname, @frag
r" E8 \) V7 y" U" d3 [( _END $ E; [5 C7 |, w2 E
-- 关闭释放游标
# Y K4 C# c# a1 N3 iCLOSE indexes 7 j% `$ C$ D- k, x; m ]8 e3 G
DEALLOCATE indexes 7 ]/ U/ l4 k# |1 p6 M1 ]
-- 报告结束时间 5 z$ b5 @, y- j* `) B. E$ m1 [
SELECT 'Finished defragmenting indexes at ' + CONVERT(VARCHAR,GETDATE())
7 w- G |/ v# x2 O% f' O-- 删除临时表 # E, m% a( L( w- c
DROP TABLE #fraglist
# e0 z; ^) _8 |2 y J# C* gGO , q( q) L) n0 Y& U5 s, P- M
使用 ( N/ q) W: k9 f8 m# q P* G
这个存储过程应该创建在master数据库里,以便你能在服务器上的任何用户数据库里使用。 . `2 K0 l6 o& H
在用户数据库里通过传递一个参数(MAXFRAG)来运行。该参数是一个百分比值。意思是任何索引的碎片扫描密度小于这个值。例如,如果你想要整理那些扫描密度小于95%的索引的碎片:
' W' N' O7 C, Q( U8 n- ~USE pubs
m0 \ D! }6 m! VGO * p( j% l- l. x: X, ?1 o! y' T* s
EXEC sp_deframent_indexes 95.00 2 W# y, n* q6 s- e0 ]* I
局限 7 t! s; w0 H, y( ^6 B
这个过程依赖于的标准是扫描密度,但扫描密度对于那些跨越多个文件的索引来说不是一个有效的标准。如果你的索引确实跨越多个文件,你需要用另一个标准(如Logical Frag)来更改这个存储过程。然而,这类更改超过本文的范围;如果你的索引跨越多个文件,你需要做更多的工作。
' L( r! i8 N* j怎样做,做什么? ' ~: f/ G3 O' D' y V
这个存储过程有两个独特的部分。 ( T' I. f# i8 s0 M4 c8 i: E
第1阶段
: j7 N! x7 J. B, M在这部分里,存储过程通过在数据库里的每个表上运行下面的命令来检查索引碎片:
8 [: E8 Y( J" Z/ I5 aDBCC SHOWCONTIG (‘tablename') WITH FAST, TABLERESULTS, ALL_INDEXES, NO_INFOMSGS
3 Y6 m' O B! [. }! D, {命令的结果存储在预先创建的临时表#fraglist里。这里就会用到DBCC SHOWCONTIG 语句的WITH TABLERESULTS的好处,仅这一点,真正的节省了太多的以前版本得到同样结果所花费的麻烦和精力。 $ A6 Q7 z: S7 n! h( D
你应该注意该存储过程工作的数据库的表的拥有者是不是dbo,通常是。我发现我最初的版本不起作用,当时一个软件经销商给我们提供的新系统的数据库里就出现了拥有者不是dbo的表。当我在这个新系统上第一次运行我的碎片整理过程时,这个程序的缺点就暴露无遗了,最后彻底失败。这个问题实际上出现在碎片整理阶段(阶段2),因为表在这里要引用表名,而在阶段1,DBCC SHOWCONTIG命令引用的时表的ID即object_id。
8 b# h' g8 k3 `: A第2阶段 0 `0 F+ h' K# M, a7 \1 _! X
这儿使用了另一个游标来循环处理表#fraglist里的记录,这些记录是那些扫描密度低于传给存储过程参数的那个阈值的表:
+ e3 [- d9 k: yDBCC DBREINDEX() 7 d. _6 k, F5 E/ P) I) s3 C
执行的结果以输出文件的形式显示在表#fraglist的内容之后,以便你能查看表和索引的碎片,正如屏幕上所显示的那样,也可以通过查看DBCC DBREINDEX()执行的结果列表来查看采取的动作。利用这些你也能推导出每个索引重建的时间。
; X1 p0 F1 q, V( _6 S n$ B输出结果是什么意思?
. k5 N: @0 I" D8 b3 g3 k" m# ^输出示例: $ K0 O P) \; H9 J( B5 {

6 U/ j' \: ]- t1 a上面是在Excel里打开的存储过程输出文本文件的一个截屏。为了简洁一些列已经删掉了。你需要用文本文件向导来打开它,选择固定列宽,打开导入从第三行起。
0 S2 F6 Y; m0 _% z" } `这里,你能够检查你选择检查的数据库里的表的扫描密度。 3 K3 E2 F# `3 K) U' q% ?
在接下来的输出文件里(DBCC SHOWCONTIG输出结果的后面),你会发现正被重建索引的每个表或索引的细节,这部分的开始和结束部分都有重建索引的开始和结束时间。如下面例子显示的那样: 9 b: \% |; J3 X" e0 u7 N

r5 r+ \2 i, b: z# ^$ [, t' |' B为什么不使用DBCC INDEXDEFRAG()去减少阻塞?
% T9 Q$ L! @/ U& S% @答案是如果你想要或者需要的话就使用它。如果你需要7×24小时的在线操作,那么DBCC DBREINDEX()的排他表锁不适合你的业务,你可以使用它来代替DBCC DBREINDEX()。然而,你需要适当改变一下语法,因为它们是不相同的(谢谢,微软!)。如果你不知道它们的区别,这里有一个简单的摘要:当运行DBCC DBREINDEX()的时候,必须对表有排他锁,因为它是一个完全的,彻头彻尾的索引重建操作。而DBCC INDEXDEFRAG()就不那么完全了,在线的操作试图改善你索引的环境而不至于引起阻塞和中断OLTP(希望如此)。我必须承认我从来不用DBCC INDEXDEFRAG(),因为很幸运的是我的系统不需要严格的7×24在线且要求不阻塞,所以我不敢担保是否有效率。我已经理解它不是和DBCC DBREINDEX一样有效率。然而它的确比什么都没有强,所以如果你的数据库运行一个全球的WEB站点并且从来不能停止,这在今天这也很普遍,那么你需要使用它来代替以改变这个存储过程。 4 }# P: s6 A0 Q0 q
添加到调度任务里
8 M7 O f$ D1 E$ [对于有相当经验的DBA来说这是一个相关的微不足道的任务,所以我在这里不会提供它的代码。作为一个独立的任务或在你存在的维护作业里的一个步骤都行。你要确保作业的步骤或作业里的输出结果是一个文本文件以便你能保存和查看所有重要的输出文件。 . c! H4 U& {, {% D/ p" t5 s
结论
, m ~3 H: z7 e; F- r3 A希望这篇文章和代码能帮助你完成一个对数据库服务器维护来说更好的更精确的方法。现在的维护窗口开销很大,所以在影响用户和执行时间上保持最小的同时也提供了有效率和良好的数据库服务器维护。用调度作业实现这个过程,小心的监控它的输出结果。 |