-
Notifications
You must be signed in to change notification settings - Fork 0
/
dbo.sp_ReindexAll.sql
352 lines (309 loc) · 11.5 KB
/
dbo.sp_ReindexAll.sql
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
/*
Returns
a) Always first recordset - one row with 4 integer columns:
ResultCode :
0 - Success
1 - Partial success - some indexes could not be rebuilt
2 - Partial success - Indexes were rebuilt, but some statistics were not updated
5 - Invalid input parameter(s)
TotalIndexesToRebuild - total count of indexes detected to be rebuild
RebuiltWithOnlineON - count of indexes rebuilt with option ONLINE = ON
RebuiltWithOnlineOFF - count of indexes rebuilt with option ONLINE = OFF (can't be rebuilt with ONLINE = ON)
TotalStatisticsToUpdate - total count of the indexes to update statistics for
StatisticsUpdated - count of the number of indexes updated
b) Always second recordset - see @errors table
c) Only when @Verbose=1, then the second recordset with detailed info about all indexes
d) Only when @Verbose=1, then the third recordset with detailed info about all indexes to update statistics on
*/
CREATE PROCEDURE [dbo].[p_ReindexAll]
(
@AllIndexTypes INT = 1, --0:Clustered Index only, 1: Clustered and Non Clustered indexes
@MaxRunTime INT = NULL, --Maximum allowed running time (in seconds)
@FragRebuildPct INT = 30, --Percentage of fragmentation at which indexes are rebuilt
@MinPages INT = 25, --do not touch tables less than 25 pages
@Verbose INT = 1, --1: Print progress messages and detailed results
@Online BIT = 1 --1: rebuild indexes online
)
AS
BEGIN
SET NOCOUNT ON
DECLARE @TotalIndexesToRebuild INT = 0,
@RebuiltWithOnlineON INT = 0,
@RebuiltWithOnlineOFF INT = 0,
@TotalStatisticsToUpdate INT = 0,
@StatisticsUpdated INT = 0
--Get start time for max run time tracking
DECLARE @MaxTime DATETIME
SELECT @MaxTime = DATEADD(ss, ISNULL(@MaxRunTime, 0), GETUTCDATE())
--Account for nulls in parameters, set to default values
SET @FragRebuildPct = ISNULL(@FragRebuildPct, 30)
SET @AllIndexTypes = ISNULL(@AllIndexTypes, 0)
SET @Verbose = ISNULL(@Verbose, 0)
SET @MinPages = ISNULL(@MinPages, 25)
SET @Online = ISNULL(@Online, 1)
--Validate parameters
IF (
(@MaxRunTime <= 0)
OR (@AllIndexTypes NOT IN (0, 1))
OR (@Verbose NOT IN (0, 1))
OR (@Online NOT IN (0, 1))
OR (@MinPages < 1)
OR (@FragRebuildPct > 100)
OR (@FragRebuildPct < 0)
)
BEGIN
PRINT 'Invalid Parameter value. Valid values are:'
PRINT 'MaxRunTime > 0,'
PRINT 'MinPages > 0'
PRINT 'FragRebuildPct in {NULL,0..100}'
PRINT 'AllIndexTypes in {0,1}'
PRINT 'Verbose in {0,1}'
PRINT 'Online in {0,1}'
SELECT 5 AS ResultCode,
@TotalIndexesToRebuild AS TotalIndexesToRebuild,
@RebuiltWithOnlineON AS RebuiltWithOnlineON,
@RebuiltWithOnlineOFF AS RebuiltWithOnlineOFF
RETURN 5
END
DECLARE @indexes TABLE
(
SchemaName SYSNAME,
TableName SYSNAME,
IndexName SYSNAME,
OldFrag INT,
NewFrag INT NULL,
processed BIT
)
DECLARE @updateStatistics TABLE
(
SchemaName SYSNAME,
TableName SYSNAME,
IndexName SYSNAME,
Processed BIT
)
DECLARE @errors TABLE
(
Number INT,
Severity INT,
STATE INT,
--Message nvarchar(4000), -- can be found by select * from sys.messages m where message_id = Number and m.language_id = 1033
OnlineOn BIT,
Statement NVARCHAR(2048)
)
INSERT INTO @indexes
SELECT SCHEMA_NAME(o.schema_id),
OBJECT_NAME(s.object_id),
i.name,
s.avg_fragmentation_in_percent,
NULL,
0
FROM sys.dm_db_index_physical_stats (DB_ID(), NULL, NULL, NULL, NULL) s
JOIN sys.objects o
ON (s.object_id = o.object_id)
JOIN sys.indexes i
ON (s.object_id = i.object_id AND s.index_id = i.index_id)
WHERE s.avg_fragmentation_in_percent > @FragRebuildPct -- defrag only if more than x% fragmented
AND i.type IN (1, @AllIndexTypes + 1) -- (1,2) -- cannot defrag non-indexes(0-heap, 1- clustered, 2-nonclustered, 3-xml)
AND s.page_count >= @MinPages -- select only if the index spans multiple pages
ORDER BY
s.avg_fragmentation_in_percent DESC
SELECT @TotalIndexesToRebuild = @@rowcount
-- Get all indexes that have a datetime column which are not set to be rebuild
INSERT INTO @updateStatistics
SELECT SCHEMA_NAME(t.schema_id),
t.name,
i.name,
0
FROM sys.indexes AS i WITH (NOLOCK)
INNER
JOIN sys.index_columns AS ic WITH (NOLOCK)
ON i.object_id = ic.object_id
AND i.index_id = ic.index_id
INNER JOIN sys.columns AS c WITH (NOLOCK)
ON c.object_id = ic.object_id
AND c.column_id = ic.column_id
INNER JOIN sys.tables t WITH (NOLOCK)
ON i.object_id = t.object_id
WHERE ic.key_ordinal = 1
AND c.system_type_id = 61
AND i.type IN (1, @AllIndexTypes + 1)
AND NOT EXISTS -- select only if not in the rebuild table
(
SELECT TableName
FROM @indexes ind
WHERE ind.SchemaName = SCHEMA_NAME(t.schema_id)
AND ind.TableName = t.Name
AND ind.IndexName = i.Name
)
ORDER BY
t.name,
i.name
SELECT @TotalStatisticsToUpdate = @@ROWCOUNT
DECLARE @SchemaName SYSNAME,
@TableName SYSNAME,
@IndexName SYSNAME,
@sqlTemplate NVARCHAR(2048),
@sql NVARCHAR(2048)
DECLARE @retry BIT,
@onlineON BIT
DECLARE IndexCursor CURSOR LOCAL
FOR
SELECT SchemaName,
TableName,
IndexName
FROM @indexes
ORDER BY
OldFrag DESC
OPEN IndexCursor
FETCH NEXT FROM IndexCursor INTO @SchemaName, @TableName, @IndexName
WHILE (
(@@FETCH_STATUS = 0)
AND ((GETUTCDATE() < @MaxTime) OR (@MaxRunTime IS NULL))
)
BEGIN
SELECT @sqlTemplate = 'ALTER INDEX [' + @IndexName + '] ' +
'ON [' + @SchemaName + '].[' + @TableName + '] REBUILD WITH ' +
'( PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, SORT_IN_TEMPDB = OFF, ONLINE = '
IF (@Online = 1)
SELECT @sql = @sqlTemplate + 'ON )'
ELSE
SELECT @sql = @sqlTemplate + 'OFF )'
SELECT @retry = 1,
@onlineON = @Online
WHILE (@retry = 1)
BEGIN
BEGIN TRY
IF (@Verbose = 1)
PRINT @sql
EXEC (@sql)
SELECT @retry = 0
IF (@onlineON = 1)
SELECT @RebuiltWithOnlineON = @RebuiltWithOnlineON + 1
ELSE
SELECT @RebuiltWithOnlineOFF = @RebuiltWithOnlineOFF + 1
END TRY
BEGIN CATCH
INSERT INTO @errors
SELECT ERROR_NUMBER(),
ERROR_SEVERITY(),
ERROR_STATE(),
@onlineON,
@sql
IF (@onlineON = 1 AND ERROR_NUMBER() = 2725)
BEGIN
-- Handle the possible exception below: rebuild index offline. Only SQL2012 has THROW
--ErrorNumber ErrorMessage
--2725 An online operation cannot be performed for index '?' because the index contains column '?' of data type text, ntext, image, varchar(max), nvarchar(max), varbinary(max), xml, or large CLR type. For a non-clustered index, the column could be an include column of the index. For a clustered index, the column could be any column of the table. If DROprEXISTING is used, the column could be part of a new or old index. The operation must be performed offline.
SELECT @sql = @sqlTemplate + 'OFF )'
SELECT @onlineON = 0
END
ELSE
SELECT @retry = 0
END CATCH
END
UPDATE @indexes
SET processed = 1
WHERE SchemaName = @SchemaName
AND TableName = @TableName
AND IndexName = @IndexName
FETCH NEXT FROM IndexCursor INTO @SchemaName, @TableName, @IndexName
END
CLOSE IndexCursor
DEALLOCATE IndexCursor
-- Update the statistics for any index that contains a datetime column that wasn't rebuilt
DECLARE UpdateCursor CURSOR LOCAL
FOR
SELECT SchemaName,
TableName,
IndexName
FROM @updateStatistics
OPEN UpdateCursor
FETCH NEXT FROM UpdateCursor INTO @SchemaName, @TableName, @IndexName
WHILE (
(@@FETCH_STATUS = 0)
AND ((GETUTCDATE() < @MaxTime) OR (@MaxRunTime IS NULL))
)
BEGIN
SELECT @sql = 'UPDATE STATISTICS [' + @SchemaName + '].[' + @TableName +
'] [' + @IndexName + ']'
IF (@Verbose = 1)
PRINT @sql
BEGIN TRY
EXEC (@sql)
SELECT @StatisticsUpdated = @StatisticsUpdated + 1
END TRY
BEGIN CATCH
INSERT INTO @errors
SELECT ERROR_NUMBER(),
ERROR_SEVERITY(),
ERROR_STATE(),
@onlineON,
@sql
END CATCH
UPDATE @updateStatistics
SET Processed = 1
WHERE SchemaName = @SchemaName
AND TableName = @TableName
AND IndexName = @IndexName
FETCH NEXT FROM UpdateCursor INTO @SchemaName, @TableName, @IndexName
END
CLOSE UpdateCursor
DEALLOCATE UpdateCursor
IF (@Verbose = 1)
BEGIN
UPDATE @indexes
SET NewFrag = avg_fragmentation_in_percent
FROM sys.dm_db_index_physical_stats (DB_ID(), NULL, NULL, NULL, NULL)
s
JOIN sys.objects o
ON (s.object_id = o.object_id)
JOIN sys.indexes i
ON (s.object_id = i.object_id AND s.index_id = i.index_id)
WHERE SchemaName = SCHEMA_NAME(o.schema_id)
AND TableName = OBJECT_NAME(s.object_id)
AND IndexName = i.name
END
DECLARE @ResultCode INT
IF EXISTS(
SELECT *
FROM @indexes
WHERE processed = 0
)
BEGIN
PRINT 'Did not process all indexes due to @MaxRunTime constraint'
SELECT @ResultCode = 1
END
ELSE
IF EXISTS(
SELECT *
FROM @updateStatistics
WHERE Processed = 0
)
BEGIN
PRINT 'Did not update all statistics due to @MaxRunTime constraint'
SELECT @ResultCode = 2
END
ELSE
BEGIN
SELECT @ResultCode = 0
END
-- Return results
SELECT @ResultCode AS ResultCode,
@TotalIndexesToRebuild AS TotalIndexesToRebuild,
@RebuiltWithOnlineON AS RebuiltWithOnlineON,
@RebuiltWithOnlineOFF AS RebuiltWithOnlineOFF,
@TotalStatisticsToUpdate AS TotalStatisticsToUpdate,
@StatisticsUpdated AS StatisticsUpdated
SELECT *
FROM @errors
IF (@Verbose = 1)
SELECT *
FROM @indexes
ORDER BY
OldFrag DESC
IF (@Verbose = 1)
SELECT *
FROM @updateStatistics
RETURN @ResultCode
END
GO