1 --T-SQL查询高级—SQL Server索引中的碎片和填充因子 重建索引 重新组织索引 设置填充因子
2
3 --http://www.cnblogs.com/CareySon/archive/2012/01/06/2313897.html
4 --在SQL Server中,存储数据的最小单位是页,每一页所能容纳的数据为8060字节.
5 --而页的组织方式是通过B树结构(表上没有聚集索引则为堆结构
6
7
8 -- 每一个叶子节点为一页,每页是不可分割的. 而SQL Server向每个页内存储数据的最小单位是表的行(Row).
9 --当叶子节点中新插入的行或更新的行使得叶子节点无法容纳当前更新或者插入的行时,分页就产生了.
10 --在分页的过程中,就会产生碎片.
11
12
13 --外部碎片
14 -- 首先,理解外部碎片的这个“外”是相对页面来说的。外部碎片指的是由于分页而产生的碎片.
15 -- 比如,我想在现有的聚集索引中插入一行,这行正好导致现有的页空间无法满足容纳新的行。从而导致了分页
16
17
18 --内部碎片
19 -- 在SQL Server中,比页更大的单位是区(Extent).一个区可以容纳8个页
20 -- .区作为磁盘分配的物理单元.所以当页分割如果跨区后,需要多次切区。
21 -- 需要更多的扫描.因为读取连续数据时会不能预读,从而造成额外的物理读,增加磁盘IO.
22
23 --创建测试表
24 USE [pratice]
25 GO
26 CREATE TABLE TableForTest
27 (
28 col1 INT,
29 col2 CHAR(999),
30 col3 VARCHAR(10)
31 )
32
33 --创建聚集索引
34 CREATE CLUSTERED INDEX CIX ON TableForTest([Col1])
35
36 --插入8条数据
37 DECLARE @var INT
38 SET @var=100
39 WHILE(@var<900)
40 BEGIN
41 INSERT INTO TableForTest(col1,col2,[Col3])
42 VALUES (@var,'xxx','')
43 SET @var=@var+100
44 END
45
46
47 --我们创建一个表,这个表每个行由int(4字节),char(999字节)和varchar(0字节组成),
48 --所以每行为1003个字节,则8行占用空间1003*8=8024字节加上一些内部开销,可以容纳在一个页面中:
49
50
51 SELECT
52 [page_count],[avg_page_space_used_in_percent],
53 [record_count],[avg_record_size_in_bytes],[avg_fragmentation_in_percent],[fragment_count]
54 FROM sys.[dm_db_index_physical_stats](DB_ID('pratice'),OBJECT_ID('dbo.TableForTest'),NULL,NULL,'sampled')
55
56
57 USE [pratice]
58 GO
59 UPDATE [dbo].[TableForTest] SET [col3]='nninininin' --只能插入10个字符
60 WHERE [col1]=100
61
62
63 SELECT
64 [page_count],[avg_page_space_used_in_percent],
65 [record_count],[avg_record_size_in_bytes],[avg_fragmentation_in_percent],[fragment_count]
66 FROM sys.[dm_db_index_physical_stats](DB_ID('pratice'),OBJECT_ID('dbo.TableForTest'),NULL,NULL,'sampled')
67
68
69
70
71
72 --外部碎片
73 USE [pratice]
74 GO
75 DELETE FROM [dbo].[TableForTest]
76 GO
77 --插入数据
78 DECLARE @var INT
79 SET @var=100
80 WHILE(@var<900)
81 BEGIN
82 INSERT INTO TableForTest(col1,col2,[Col3])
83 VALUES (@var,'xxx','')
84 SET @var=@var+20
85 END
86
87 --往聚集索引里额外插入数据,造成碎片
88 DECLARE @var INT
89 SET @var=100
90 WHILE(@var<900)
91 BEGIN
92 INSERT INTO TableForTest(col1,col2,[Col3])
93 VALUES (@var,'xxx','')
94 SET @var=@var+100
95 END
96
97
98 --看逻辑I/O
99 SET STATISTICS IO ON
100 SELECT * FROM [dbo].[TableForTest]
101 ALTER INDEX [CIX] ON [TableForTest] REBUILD
102 --ALTER INDEX [dbo].[TableForTest].[CIX] ON [dbo].[TableForTest] REORGANIZE
103 SET STATISTICS IO ON
104 SELECT * FROM [dbo].[TableForTest]
105 SET STATISTICS IO OFF
106
107
108
109 --对于碎片的解决办法
110 --基本上所有解决办法都是基于对索引的重建和整理,只是方式不同
111 --
112 --1.删除索引并重建
113 --这种方式并不好.在删除索引期间,索引不可用.会导致阻塞发生。而对于删除聚集索引,
114 --则会导致对应的非聚集索引重建两次(删除时重建,建立时再重建).虽然这种方法并不好,但是对于索引的整理最为有效
115
116
117 --2.使用DROP_EXISTING语句重建索引
118 -- 为了避免重建两次索引,使用DROP_EXISTING语句重建索引,因为这个语句是原子性的,
119 --不会导致非聚集索引重建两次,但同样的,这种方式也会造成阻塞
120
121
122
123 --3.如前面文章所示,使用ALTER INDEX REBUILD语句重建索引
124 -- 使用这个语句同样也是重建索引,但是通过动态重建索引而不需要卸载并重建索引.是优于前两种方法的,
125 --但依旧会造成阻塞。可以通过ONLINE关键字减少锁,但会造成重建时间加长.
126 ALTER INDEX [CIX] ON [TableForTest] REBUILD WITH(online=ON)
127
128
129
130 --4.使用ALTER INDEX REORGANIZE
131 --ALTER INDEX [dbo].[TableForTest].[CIX] ON [dbo].[TableForTest] REORGANIZE
132 --这种方式不会重建索引,也不会生成新的页,仅仅是整理,当遇到加锁的页时跳过,所以不会造成阻塞。
133 --但同时,整理效果会差于前三种.
134
135
136
137
138 --理解填充因子
139 -- 重建索引固然可以解决碎片的问题.但是重建索引的代价不仅仅是麻烦,还会造成阻塞。
140 -- 影响使用.而对于数据比较少的情况下,重建索引代价并不大。而当索引本身超过百兆的时候。重建索引的时间将会很让人蛋疼.
141 --
142 -- 填充因子的作用正是如此。对于默认值来说,填充因子为0(0和100表示的是一个概念),则表示页面可以100%使用。
143 -- 所以会遇到前面update或insert时,空间不足导致分页.通过设置填充因子,可以设置页面的使用程度
144
145
146
147 USE [pratice]
148 GO
149 DELETE FROM [dbo].[TableForTest]
150 GO
151 --插入数据
152 DECLARE @var INT
153 SET @var=100
154 WHILE(@var<3200)
155 BEGIN
156 INSERT INTO TableForTest(col1,col2,[Col3])
157 VALUES (@var,'xxx','')
158 SET @var=@var+100
159 END
160
161 --占4页
162 SELECT
163 [page_count],[avg_page_space_used_in_percent],
164 [record_count],[avg_record_size_in_bytes],[avg_fragmentation_in_percent],[fragment_count]
165 FROM sys.[dm_db_index_physical_stats](DB_ID('pratice'),OBJECT_ID('dbo.TableForTest'),NULL,NULL,'sampled')
166
167
168
169 --增加填充因子为80
170 ALTER INDEX [CIX] ON [TableForTest] REBUILD WITH (FILLFACTOR=80)
171
172 USE [pratice]
173 GO
174 INSERT INTO [dbo].[TableForTest]
175 ( [col1], [col2], [col3] )
176 VALUES (650,'xxx','' )
177
178
179
180 --[avg_page_space_used_in_percent]接近填充因子的80
181 SELECT
182 [page_count],[avg_page_space_used_in_percent],
183 [record_count],[avg_record_size_in_bytes],[avg_fragmentation_in_percent],[fragment_count]
184 FROM sys.[dm_db_index_physical_stats](DB_ID('pratice'),OBJECT_ID('dbo.TableForTest'),NULL,NULL,'sampled')
185
186
187
188
189
190
191 --使用填充因子会减少更新或者插入时的分页次数,但由于需要更多的页,则会对应的损失查找性能.
192
193
194 --如何设置填充因子的值
195 -- 如何设置填充因子的值并没有一个公式或者理念可以准确的设置。使用填充因子虽然可以减少更新或者插入时的分页,
196 -- 但同时因为需要更多的页,所以降低了查询的性能和占用更多的磁盘空间.如何设置这个值进行trade-off需要根据具体的情况来看.
197 --
198 -- 具体情况要根据对于表的读写比例来看,我这里给出我认为比较合适的值:
199 --
200 -- 1.当读写比例大于100:1时,不要设置填充因子,100%填充
201 --
202 -- 2.当写的次数大于读的次数时,设置50%-70%填充
203 --
204 -- 3.当读写比例位于两者之间时80%-90%填充
205 --
206 -- 上面的数据仅仅是我的看法,具体设置的数据还要根据具体情况进行测试才能找到最优.