-
Notifications
You must be signed in to change notification settings - Fork 747
Expand file tree
/
Copy pathusp_AdaptiveIndexDefrag.sql
More file actions
3338 lines (2864 loc) · 187 KB
/
usp_AdaptiveIndexDefrag.sql
File metadata and controls
3338 lines (2864 loc) · 187 KB
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
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
-- If you are using AdaptiveIndexDefrag together with the maintenance plans in http://blogs.msdn.com/b/blogdoezequiel/archive/2012/09/18/about-maintenance-plans-grooming-sql-server.aspx
-- please note that the job that runs AdaptiveIndexDefrag is expecting msdb. As such, change the database context accordingly.
-- For deployment in Azure SQL Database, remove or comment the USE statement below.
USE msdb
GO
SET NOCOUNT ON;
DECLARE @deploymode bit
SET @deploymode = 0 /* 0 = Upgrade from immediately previous version, preserving all historic data;
1 = Rewrite all objects, disregarding historic data */
/* Scroll down to line 429 to the see notes, disclaimers, and licensing information */
RAISERROR('Droping existing objects', 0, 42) WITH NOWAIT;
IF EXISTS(SELECT [object_id] FROM sys.views WHERE [name] = 'vw_CurrentExecStats')
DROP VIEW vw_CurrentExecStats
IF EXISTS(SELECT [object_id] FROM sys.views WHERE [name] = 'vw_ErrLst30Days')
DROP VIEW vw_ErrLst30Days
IF EXISTS(SELECT [object_id] FROM sys.views WHERE [name] = 'vw_LastRun_Log')
DROP VIEW vw_LastRun_Log
IF EXISTS(SELECT [object_id] FROM sys.views WHERE [name] = 'vw_ErrLst24Hrs')
DROP VIEW vw_ErrLst24Hrs
IF EXISTS(SELECT [object_id] FROM sys.views WHERE [name] = 'vw_AvgSamplingLst30Days')
DROP VIEW vw_AvgSamplingLst30Days
IF EXISTS(SELECT [object_id] FROM sys.views WHERE [name] = 'vw_AvgTimeLst30Days ')
DROP VIEW vw_AvgTimeLst30Days
IF EXISTS(SELECT [object_id] FROM sys.views WHERE [name] = 'vw_AvgFragLst30Days')
DROP VIEW vw_AvgFragLst30Days
IF EXISTS(SELECT [object_id] FROM sys.views WHERE [name] = 'vw_AvgLargestLst30Days')
DROP VIEW vw_AvgLargestLst30Days
IF EXISTS(SELECT [object_id] FROM sys.views WHERE [name] = 'vw_AvgMostUsedLst30Days')
DROP VIEW vw_AvgMostUsedLst30Days
IF @deploymode = 0
BEGIN
RAISERROR('Preserving historic data', 0, 42) WITH NOWAIT;
IF EXISTS(SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_log') AND NOT EXISTS (SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_log_old')
BEGIN
EXEC sp_rename 'tbl_AdaptiveIndexDefrag_log', 'tbl_AdaptiveIndexDefrag_log_old';
EXEC sp_rename N'tbl_AdaptiveIndexDefrag_log_old.PK_AdaptiveIndexDefrag_log', N'PK_AdaptiveIndexDefrag_log_old', N'INDEX';
END;
IF EXISTS(SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Analysis_log') AND NOT EXISTS (SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Analysis_log_old')
BEGIN
EXEC sp_rename 'tbl_AdaptiveIndexDefrag_Analysis_log', 'tbl_AdaptiveIndexDefrag_Analysis_log_old';
EXEC sp_rename N'tbl_AdaptiveIndexDefrag_Analysis_log_old.PK_AdaptiveIndexDefrag_Analysis_log', N'PK_AdaptiveIndexDefrag_Analysis_log_old', N'INDEX';
END;
IF EXISTS(SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Stats_log') AND NOT EXISTS (SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Stats_log_old')
BEGIN
EXEC sp_rename 'tbl_AdaptiveIndexDefrag_Stats_log', 'tbl_AdaptiveIndexDefrag_Stats_log_old';
EXEC sp_rename N'tbl_AdaptiveIndexDefrag_Stats_log_old.PK_AdaptiveIndexDefrag_Stats_log', N'PK_AdaptiveIndexDefrag_Stats_log_old', N'INDEX';
END;
IF EXISTS(SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Exceptions') AND NOT EXISTS (SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Exceptions_old')
BEGIN
EXEC sp_rename 'tbl_AdaptiveIndexDefrag_Exceptions', 'tbl_AdaptiveIndexDefrag_Exceptions_old';
EXEC sp_rename N'tbl_AdaptiveIndexDefrag_Exceptions_old.PK_AdaptiveIndexDefrag_Exceptions', N'PK_AdaptiveIndexDefrag_Exceptions_old', N'INDEX';
END;
IF EXISTS(SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Working') AND NOT EXISTS (SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Working_old')
BEGIN
EXEC sp_rename 'tbl_AdaptiveIndexDefrag_Working', 'tbl_AdaptiveIndexDefrag_Working_old';
EXEC sp_rename N'tbl_AdaptiveIndexDefrag_Working_old.PK_AdaptiveIndexDefrag_Working', N'PK_AdaptiveIndexDefrag_Working_old', N'INDEX';
END;
IF EXISTS(SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Stats_Working') AND NOT EXISTS (SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Stats_Working_old')
BEGIN
EXEC sp_rename 'tbl_AdaptiveIndexDefrag_Stats_Working', 'tbl_AdaptiveIndexDefrag_Stats_Working_old';
EXEC sp_rename N'tbl_AdaptiveIndexDefrag_Stats_Working_old.PK_AdaptiveIndexDefrag_Stats_Working', N'PK_AdaptiveIndexDefrag_Stats_Working_old', N'INDEX';
END;
IF EXISTS(SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_IxDisableStatus') AND NOT EXISTS (SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_IxDisableStatus_old')
BEGIN
EXEC sp_rename 'tbl_AdaptiveIndexDefrag_IxDisableStatus', 'tbl_AdaptiveIndexDefrag_IxDisableStatus_old';
EXEC sp_rename N'tbl_AdaptiveIndexDefrag_IxDisableStatus_old.PK_AdaptiveIndexDefrag_IxDisableStatus', N'PK_AdaptiveIndexDefrag_IxDisableStatus_old', N'INDEX';
END;
END
IF EXISTS(SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_log')
DROP TABLE tbl_AdaptiveIndexDefrag_log;
IF EXISTS(SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Analysis_log')
DROP TABLE tbl_AdaptiveIndexDefrag_Analysis_log
IF EXISTS(SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Stats_log')
DROP TABLE tbl_AdaptiveIndexDefrag_Stats_log;
IF EXISTS(SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Exceptions')
DROP TABLE tbl_AdaptiveIndexDefrag_Exceptions;
IF EXISTS(SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Working')
DROP TABLE tbl_AdaptiveIndexDefrag_Working;
IF EXISTS(SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Stats_Working')
DROP TABLE tbl_AdaptiveIndexDefrag_Stats_Working;
IF EXISTS(SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_IxDisableStatus')
DROP TABLE tbl_AdaptiveIndexDefrag_IxDisableStatus;
IF OBJECTPROPERTY(OBJECT_ID('dbo.usp_AdaptiveIndexDefrag_PurgeLogs'), N'IsProcedure') = 1
DROP PROCEDURE dbo.usp_AdaptiveIndexDefrag_PurgeLogs;
IF OBJECTPROPERTY(OBJECT_ID('dbo.usp_AdaptiveIndexDefrag_Exceptions'), N'IsProcedure') = 1
DROP PROCEDURE dbo.usp_AdaptiveIndexDefrag_Exceptions;
IF OBJECTPROPERTY(OBJECT_ID('dbo.usp_AdaptiveIndexDefrag_Exclusions'), N'IsProcedure') = 1
DROP PROCEDURE dbo.usp_AdaptiveIndexDefrag_Exclusions;
IF OBJECTPROPERTY(OBJECT_ID('dbo.usp_CurrentExecStats'), N'IsProcedure') = 1
DROP PROCEDURE dbo.usp_CurrentExecStats;
IF OBJECTPROPERTY(OBJECT_ID('dbo.usp_AdaptiveIndexDefrag_CurrentExecStats'), N'IsProcedure') = 1
DROP PROCEDURE dbo.usp_AdaptiveIndexDefrag_CurrentExecStats;
IF NOT EXISTS (SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_log')
CREATE TABLE dbo.tbl_AdaptiveIndexDefrag_log
(indexDefrag_id int identity(1,1) NOT NULL
, dbID int NOT NULL
, dbName NVARCHAR(128) NOT NULL
, objectID int NOT NULL
, objectName NVARCHAR(256) NULL
, indexID int NOT NULL
, indexName NVARCHAR(256) NULL
, partitionNumber smallint
, fragmentation float NOT NULL
, page_count bigint NOT NULL
, range_scan_count bigint NULL
, fill_factor int NULL
, dateTimeStart DATETIME NOT NULL
, dateTimeEnd DATETIME NULL
, durationSeconds int NULL
, sqlStatement VARCHAR(4000) NULL
, errorMessage VARCHAR(1000) NULL
CONSTRAINT PK_AdaptiveIndexDefrag_log PRIMARY KEY CLUSTERED (indexDefrag_id));
CREATE INDEX IX_tbl_AdaptiveIndexDefrag_log ON [dbo].[tbl_AdaptiveIndexDefrag_log] ([dbID], [objectID], [indexName], [dateTimeEnd]);
CREATE INDEX IX_tbl_AdaptiveIndexDefrag_log_dateTimeEnd ON [dbo].[tbl_AdaptiveIndexDefrag_log] ([indexDefrag_id], [dateTimeEnd]);
RAISERROR('tbl_AdaptiveIndexDefrag_log table created', 0, 42) WITH NOWAIT;
IF NOT EXISTS (SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Analysis_log')
CREATE TABLE dbo.tbl_AdaptiveIndexDefrag_Analysis_log
(analysis_id int identity(1,1) NOT NULL
, [Operation] NCHAR(5)
, dbID int NOT NULL
, dbName NVARCHAR(128) NOT NULL
, objectID int NOT NULL
, objectName NVARCHAR(256) NULL
, index_or_stat_ID int NOT NULL
, partitionNumber smallint
, dateTimeStart DATETIME NOT NULL
, dateTimeEnd DATETIME NULL
, durationSeconds int NULL
, errorMessage VARCHAR(1000) NULL
CONSTRAINT PK_AdaptiveIndexDefrag_Analysis_log PRIMARY KEY CLUSTERED (analysis_id));
CREATE INDEX IX_tbl_AdaptiveIndexDefrag_Analysis_log_dateTimeEnd ON [dbo].[tbl_AdaptiveIndexDefrag_Analysis_log] ([analysis_id], [dateTimeEnd]);
RAISERROR('tbl_AdaptiveIndexDefrag_Analysis_log table created', 0, 42) WITH NOWAIT;
IF NOT EXISTS (SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Exceptions')
CREATE TABLE dbo.tbl_AdaptiveIndexDefrag_Exceptions
(dbID int NOT NULL
, objectID int NOT NULL
, indexID int NOT NULL
, dbName NVARCHAR(128) NOT NULL
, objectName NVARCHAR(256) NOT NULL
, indexName NVARCHAR(256) NOT NULL
, exclusionMask int NOT NULL
/* Same as in msdb.dbo.sysschedules:
1=Sunday, 2=Monday, 4=Tuesday, 8=Wednesday, 16=Thursday, 32=Friday, 64=Saturday, 0=AllWeek, -1=Never
For multiple days, sum the corresponding values*/
CONSTRAINT PK_AdaptiveIndexDefrag_Exceptions PRIMARY KEY CLUSTERED (dbID, objectID, indexID));
RAISERROR('tbl_AdaptiveIndexDefrag_Exceptions table created', 0, 42) WITH NOWAIT;
IF NOT EXISTS (SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Working')
CREATE TABLE dbo.tbl_AdaptiveIndexDefrag_Working
(dbID int
, objectID int
, indexID int
, partitionNumber smallint
, dbName NVARCHAR(128)
, schemaName NVARCHAR(128) NULL
, objectName NVARCHAR(256) NULL
, indexName NVARCHAR(256) NULL
, fragmentation float
, page_count int
, is_primary_key bit
, fill_factor int
, is_disabled bit
, is_padded bit
, is_hypothetical bit
, has_filter bit
, allow_page_locks bit
, compression_type NVARCHAR(60) NULL -- 0 = NONE; 1 = ROW; 2 = PAGE; 3 = COLUMNSTORE; 4 = COLUMNSTORE_ARCHIVE
, range_scan_count bigint NULL
, record_count bigint
, [type] tinyint -- 0 = Heap; 1 = Clustered; 2 = Nonclustered; 3 = XML; 4 = Spatial; 5 = Clustered columnstore; 6 = Nonclustered columnstore; 7 = Nonclustered hash
, scanDate DATETIME
, defragDate DATETIME NULL
, printStatus bit DEFAULT(0) -- Used for loop control when printing the SQL commands.
, exclusionMask int DEFAULT(0)
CONSTRAINT PK_AdaptiveIndexDefrag_Working PRIMARY KEY CLUSTERED(dbID, objectID, indexID, partitionNumber));
RAISERROR('tbl_AdaptiveIndexDefrag_Working table created', 0, 42) WITH NOWAIT;
IF NOT EXISTS (SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Stats_Working')
CREATE TABLE dbo.tbl_AdaptiveIndexDefrag_Stats_Working
(dbID int
, objectID int
, statsID int
, partitionNumber smallint
, dbName NVARCHAR(128)
, schemaName NVARCHAR(128) NULL
, objectName NVARCHAR(256) NULL
, statsName NVARCHAR(256)
, [no_recompute] bit
, [is_incremental] bit
, scanDate DATETIME
, updateDate DATETIME NULL
, printStatus bit DEFAULT(0) -- Used for loop control when printing the SQL commands.
CONSTRAINT PK_AdaptiveIndexDefrag_Stats_Working PRIMARY KEY CLUSTERED(dbID, objectID, statsID, partitionNumber));
RAISERROR('tbl_AdaptiveIndexDefrag_Stats_Working table created', 0, 42) WITH NOWAIT;
IF NOT EXISTS (SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Stats_log')
CREATE TABLE dbo.tbl_AdaptiveIndexDefrag_Stats_log
(statsUpdate_id int identity(1,1) NOT NULL
, dbID int NOT NULL
, dbName NVARCHAR(128) NULL
, objectID int NULL
, objectName NVARCHAR(256) NULL
, statsID int NOT NULL
, statsName NVARCHAR(256) NULL
, partitionNumber smallint
, [rows] bigint
, rows_sampled bigint
, modification_counter bigint
, [no_recompute] bit
, dateTimeStart DATETIME NOT NULL
, dateTimeEnd DATETIME NULL
, durationSeconds int NULL
, sqlStatement VARCHAR(4000) NULL
, errorMessage VARCHAR(1000) NULL
CONSTRAINT PK_AdaptiveIndexDefrag_Stats_log PRIMARY KEY CLUSTERED (statsUpdate_id));
CREATE INDEX IX_tbl_AdaptiveIndexDefrag_Stats_log ON [dbo].[tbl_AdaptiveIndexDefrag_Stats_log] ([dbID], [objectID], [statsName], [dateTimeEnd]);
RAISERROR('tbl_AdaptiveIndexDefrag_Stats_log table created', 0, 42) WITH NOWAIT;
IF NOT EXISTS (SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_IxDisableStatus')
CREATE TABLE dbo.tbl_AdaptiveIndexDefrag_IxDisableStatus
(disable_id int identity(1,1) NOT NULL
, dbID int NOT NULL
, objectID int NOT NULL
, indexID int NOT NULL
, [is_disabled] bit
, dateTimeChange DATETIME NOT NULL
CONSTRAINT PK_AdaptiveIndexDefrag_IxDisableStatus PRIMARY KEY CLUSTERED (disable_id));
RAISERROR('tbl_AdaptiveIndexDefrag_IxDisableStatus table created', 0, 42) WITH NOWAIT;
IF @deploymode = 0
BEGIN
RAISERROR('Copying old data...', 0, 42) WITH NOWAIT;
BEGIN TRY
IF EXISTS (SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_log') AND EXISTS(SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_log_old')
INSERT INTO dbo.tbl_AdaptiveIndexDefrag_log ([dbID],[dbName],[objectID],[objectName]
,[indexID],[indexName],[partitionNumber],[fragmentation],[page_count]
,[range_scan_count],[fill_factor],[dateTimeStart],[dateTimeEnd]
,[durationSeconds],[sqlStatement],[errorMessage])
SELECT [dbID],[dbName],[objectID],[objectName],[indexID]
,[indexName],[partitionNumber],[fragmentation],[page_count]
,[range_scan_count],[fill_factor],[dateTimeStart],[dateTimeEnd]
,[durationSeconds],[sqlStatement],[errorMessage]
FROM dbo.tbl_AdaptiveIndexDefrag_log_old;
IF EXISTS (SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Analysis_log') AND EXISTS(SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Analysis_log_old')
INSERT INTO dbo.tbl_AdaptiveIndexDefrag_Analysis_log ([Operation],[dbID],[dbName],[objectID],[objectName]
,[index_or_stat_ID],[partitionNumber],[dateTimeStart],[dateTimeEnd]
,[durationSeconds],[errorMessage])
SELECT [Operation],[dbID],[dbName],[objectID],[objectName],[index_or_stat_ID]
,[partitionNumber],[dateTimeStart],[dateTimeEnd]
,[durationSeconds],[errorMessage]
FROM dbo.tbl_AdaptiveIndexDefrag_Analysis_log_old;
IF EXISTS (SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Stats_log') AND EXISTS(SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Stats_log_old')
BEGIN
IF (SELECT COUNT(sc.column_id) FROM sys.tables st INNER JOIN sys.columns sc ON st.[object_id] = sc.[object_id] WHERE (sc.[name] = 'partitionNumber' OR sc.[name] = 'rows' OR sc.[name] = 'rows_sampled' OR sc.[name] = 'modification_counter') AND st.[name] = 'tbl_AdaptiveIndexDefrag_Stats_log_old') = 4
BEGIN
EXEC ('INSERT INTO dbo.tbl_AdaptiveIndexDefrag_Stats_log ([dbID],[dbName],[objectID],[objectName],[statsID],[statsName],[partitionNumber],[rows],rows_sampled,modification_counter,[no_recompute],[dateTimeStart],[dateTimeEnd],[durationSeconds],[sqlStatement],[errorMessage])
SELECT [dbID],[dbName],[objectID],[objectName],[statsID],[statsName],[partitionNumber],[rows],rows_sampled,modification_counter,[no_recompute],[dateTimeStart],[dateTimeEnd],[durationSeconds],[sqlStatement],[errorMessage]
FROM dbo.tbl_AdaptiveIndexDefrag_Stats_log_old;')
END
ELSE IF (SELECT COUNT(sc.column_id) FROM sys.tables st INNER JOIN sys.columns sc ON st.[object_id] = sc.[object_id] WHERE sc.[name] = 'partitionNumber' AND st.[name] = 'tbl_AdaptiveIndexDefrag_Stats_log_old') = 1
AND (SELECT COUNT(sc.column_id) FROM sys.tables st INNER JOIN sys.columns sc ON st.[object_id] = sc.[object_id] WHERE (sc.[name] = 'rows' OR sc.[name] = 'rows_sampled' OR sc.[name] = 'modification_counter') AND st.[name] = 'tbl_AdaptiveIndexDefrag_Stats_log_old') = 0
BEGIN
EXEC ('INSERT INTO dbo.tbl_AdaptiveIndexDefrag_Stats_log ([dbID],[dbName],[objectID],[objectName],[statsID],[statsName],[partitionNumber],[rows],rows_sampled,modification_counter,[no_recompute],[dateTimeStart],[dateTimeEnd],[durationSeconds],[sqlStatement],[errorMessage])
SELECT [dbID],[dbName],[objectID],[objectName],[statsID],[statsName],[partitionNumber],-1,-1,-1,[no_recompute],[dateTimeStart],[dateTimeEnd],[durationSeconds],[sqlStatement],[errorMessage]
FROM dbo.tbl_AdaptiveIndexDefrag_Stats_log_old;')
END
ELSE
BEGIN
EXEC ('INSERT INTO dbo.tbl_AdaptiveIndexDefrag_Stats_log ([dbID],[dbName],[objectID],[objectName],[statsID],[statsName],[partitionNumber],[rows],rows_sampled,modification_counter,[no_recompute],[dateTimeStart],[dateTimeEnd],[durationSeconds],[sqlStatement],[errorMessage])
SELECT [dbID],[dbName],[objectID],[objectName],[statsID],[statsName],1,-1,-1,-1,[no_recompute],[dateTimeStart],[dateTimeEnd],[durationSeconds],[sqlStatement],[errorMessage]
FROM dbo.tbl_AdaptiveIndexDefrag_Stats_log_old;')
END
END
IF EXISTS(SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Exceptions') AND EXISTS(SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Exceptions_old')
INSERT INTO dbo.tbl_AdaptiveIndexDefrag_Exceptions ([dbID],[objectID],[indexID],[dbName],[objectName],[indexName],[exclusionMask])
SELECT [dbID],[objectID],[indexID],[dbName],[objectName],[indexName],[exclusionMask]
FROM dbo.tbl_AdaptiveIndexDefrag_Exceptions_old;
IF EXISTS(SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Working') AND EXISTS(SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Working_old')
BEGIN
IF EXISTS (SELECT sc.column_id FROM sys.tables st INNER JOIN sys.columns sc ON st.[object_id] = sc.[object_id] WHERE (sc.[name] = 'compression_type') AND st.[name] = 'tbl_AdaptiveIndexDefrag_Working_old')
BEGIN
EXEC ('INSERT INTO dbo.tbl_AdaptiveIndexDefrag_Working ([dbID],[objectID],[indexID],[partitionNumber],[dbName],[schemaName],[objectName],[indexName],[fragmentation],[page_count],[fill_factor],[is_disabled],[is_padded],[is_hypothetical],[has_filter],[allow_page_locks],[compression_type],[range_scan_count],[record_count],[type],[scanDate],[defragDate],[printStatus],[exclusionMask])
SELECT [dbID],[objectID],[indexID],[partitionNumber],[dbName],[schemaName],[objectName],[indexName],[fragmentation],[page_count],[fill_factor],[is_disabled],[is_padded],[is_hypothetical],[has_filter],[allow_page_locks],[compression_type],[range_scan_count],[record_count],[type],[scanDate],[defragDate],[printStatus],[exclusionMask]
FROM dbo.tbl_AdaptiveIndexDefrag_Working_old;')
END
ELSE
BEGIN
EXEC ('INSERT INTO dbo.tbl_AdaptiveIndexDefrag_Working ([dbID],[objectID],[indexID],[partitionNumber],[dbName],[schemaName],[objectName],[indexName],[fragmentation],[page_count],[fill_factor],[is_disabled],[is_padded],[is_hypothetical],[has_filter],[allow_page_locks],[range_scan_count],[record_count],[type],[scanDate],[defragDate],[printStatus],[exclusionMask])
SELECT [dbID],[objectID],[indexID],[partitionNumber],[dbName],[schemaName],[objectName],[indexName],[fragmentation],[page_count],[fill_factor],[is_disabled],[is_padded],[is_hypothetical],[has_filter],[allow_page_locks],[range_scan_count],[record_count],[type],[scanDate],[defragDate],[printStatus],[exclusionMask]
FROM dbo.tbl_AdaptiveIndexDefrag_Working_old;')
END
END
IF EXISTS(SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Stats_Working') AND EXISTS(SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Stats_Working_old')
BEGIN
IF EXISTS (SELECT sc.column_id FROM sys.tables st INNER JOIN sys.columns sc ON st.[object_id] = sc.[object_id] WHERE (sc.[name] = 'partitionNumber' OR sc.[name] = 'is_incremental') AND st.[name] = 'tbl_AdaptiveIndexDefrag_Stats_Working_old')
BEGIN
EXEC ('INSERT INTO dbo.tbl_AdaptiveIndexDefrag_Stats_Working ([dbID],[objectID],[statsID],[dbName],[schemaName],[objectName],[statsName],[partitionNumber],[no_recompute],[is_incremental],[scanDate],[updateDate],[printStatus])
SELECT [dbID],[objectID],[statsID],[dbName],[schemaName],[objectName],[statsName],[partitionNumber],[no_recompute],[is_incremental],[scanDate],[updateDate],[printStatus]
FROM dbo.tbl_AdaptiveIndexDefrag_Stats_Working_old;')
END
ELSE
BEGIN
EXEC ('INSERT INTO dbo.tbl_AdaptiveIndexDefrag_Stats_Working ([dbID],[objectID],[statsID],[dbName],[schemaName],[objectName],[statsName],[partitionNumber],[no_recompute],[is_incremental],[scanDate],[updateDate],[printStatus])
SELECT [dbID],[objectID],[statsID],[dbName],[schemaName],[objectName],[statsName],1,[no_recompute],0,[scanDate],[updateDate],[printStatus]
FROM dbo.tbl_AdaptiveIndexDefrag_Stats_Working_old;')
END
END
IF EXISTS(SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_IxDisableStatus') AND EXISTS(SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_IxDisableStatus_old')
INSERT INTO dbo.tbl_AdaptiveIndexDefrag_IxDisableStatus ([dbID],[objectID],[indexID],[is_disabled],dateTimeChange)
SELECT [dbID],[objectID],[indexID],[is_disabled],dateTimeChange
FROM dbo.tbl_AdaptiveIndexDefrag_IxDisableStatus_old;
END TRY
BEGIN CATCH
RAISERROR('Could not copy old data back. Check for any previous errors.', 15, 42) WITH NOWAIT;
RETURN
END CATCH
RAISERROR('Done copying old data...', 0, 42) WITH NOWAIT;
IF EXISTS (SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_log_old')
BEGIN
IF (SELECT COUNT(*) FROM dbo.tbl_AdaptiveIndexDefrag_log_old) = (SELECT COUNT(*) FROM dbo.tbl_AdaptiveIndexDefrag_log)
DROP TABLE tbl_AdaptiveIndexDefrag_log_old
END;
IF EXISTS (SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Analysis_log_old')
BEGIN
IF (SELECT COUNT(*) FROM dbo.tbl_AdaptiveIndexDefrag_Analysis_log_old) = (SELECT COUNT(*) FROM dbo.tbl_AdaptiveIndexDefrag_Analysis_log)
DROP TABLE tbl_AdaptiveIndexDefrag_Analysis_log_old
END;
IF EXISTS (SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Stats_log_old')
BEGIN
IF (SELECT COUNT(*) FROM dbo.tbl_AdaptiveIndexDefrag_Stats_log_old) = (SELECT COUNT(*) FROM dbo.tbl_AdaptiveIndexDefrag_Stats_log)
DROP TABLE tbl_AdaptiveIndexDefrag_Stats_log_old
END;
IF EXISTS (SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Exceptions_old')
BEGIN
IF (SELECT COUNT(*) FROM dbo.tbl_AdaptiveIndexDefrag_Exceptions_old) = (SELECT COUNT(*) FROM dbo.tbl_AdaptiveIndexDefrag_Exceptions)
DROP TABLE tbl_AdaptiveIndexDefrag_Exceptions_old
END;
IF EXISTS (SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Working_old')
BEGIN
IF (SELECT COUNT(*) FROM dbo.tbl_AdaptiveIndexDefrag_Working_old) = (SELECT COUNT(*) FROM dbo.tbl_AdaptiveIndexDefrag_Working)
DROP TABLE tbl_AdaptiveIndexDefrag_Working_old
END;
IF EXISTS (SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Stats_Working_old')
BEGIN
IF (SELECT COUNT(*) FROM dbo.tbl_AdaptiveIndexDefrag_Stats_Working_old) = (SELECT COUNT(*) FROM dbo.tbl_AdaptiveIndexDefrag_Stats_Working)
DROP TABLE tbl_AdaptiveIndexDefrag_Stats_Working_old
END;
IF EXISTS (SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_IxDisableStatus_old')
BEGIN
IF (SELECT COUNT(*) FROM dbo.tbl_AdaptiveIndexDefrag_IxDisableStatus_old) = (SELECT COUNT(*) FROM dbo.tbl_AdaptiveIndexDefrag_IxDisableStatus)
DROP TABLE tbl_AdaptiveIndexDefrag_IxDisableStatus_old
END;
IF EXISTS (SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Stats_Working_old')
OR EXISTS(SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_log_old')
OR EXISTS(SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Analysis_log_old')
OR EXISTS(SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Stats_log_old')
OR EXISTS(SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Exceptions_old')
OR EXISTS(SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Working_old')
OR EXISTS(SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_IxDisableStatus_old')
BEGIN
RAISERROR('Data mismatch. Keeping some or all old tables as <tablename_old>.', 0, 42) WITH NOWAIT;
END
ELSE
BEGIN
RAISERROR('Removed old tables...', 0, 42) WITH NOWAIT;
END
END;
GO
------------------------------------------------------------------------------------------------------------------------------
IF OBJECTPROPERTY(OBJECT_ID('dbo.usp_AdaptiveIndexDefrag'), N'IsProcedure') = 1
BEGIN
DROP PROCEDURE dbo.usp_AdaptiveIndexDefrag;
PRINT 'Procedure usp_AdaptiveIndexDefrag dropped';
END;
GO
CREATE PROCEDURE dbo.usp_AdaptiveIndexDefrag
@Exec_Print bit = 1
/* 1 = execute commands; 0 = print commands only */
, @printCmds bit = 0
/* 1 = print commands; 0 = do not print commands */
, @outputResults bit = 0
/* 1 = output fragmentation information;
0 = do not output */
, @debugMode bit = 0
/* display some useful comments to help determine if/where issues occur
1 = display debug comments;
0 = do not display debug comments*/
, @timeLimit int = 480 /* defaults to 8 hours */
/* Optional time limitation; expressed in minutes */
, @dbScope NVARCHAR(256) = NULL
/* Option to specify a database name; NULL will return all */
, @tblName NVARCHAR(1000) = NULL -- schema.table_name
/* Option to specify a table name; NULL will return all */
, @defragOrderColumn NVARCHAR(20) = 'range_scan_count'
/* Valid options are: range_scan_count, fragmentation, page_count */
, @defragSortOrder NVARCHAR(4) = 'DESC'
/* Valid options are: ASC, DESC */
, @forceRescan bit = 0
/* Whether to force a rescan of indexes into the tbl_AdaptiveIndexDefrag_Working table or not;
1 = force, 0 = use existing scan when available, used to continue where previous run left off */
, @defragDelay CHAR(8) = '00:00:05'
/* time to wait between defrag commands */
, @ixtypeOption bit = NULL
/* NULL = all indexes will be defragmented; 1 = only Clustered indexes will be defragmented; 0 = only Non-Clustered indexes will be defragmented (includes XML and Spatial); */
, @minFragmentation float = 5.0
/* in percent, will not defrag if fragmentation is less than specified */
, @rebuildThreshold float = 30.0
/* in percent, greater than @rebuildThreshold will result in rebuild instead of reorg */
, @rebuildThreshold_cs float = 10.0
/* in percent, greater than @rebuildThreshold_cs will result in rebuild the columnstore index */
, @minPageCount int = 8
/* Recommended is defrag when index is at least > 1 extent (8 pages) */
, @maxPageCount int = NULL
/* NULL = no limit */
, @fillfactor bit = 1
/* 1 = original from when the index was created or last defraged;
0 = default fillfactor */
, @scanMode VARCHAR(10) = N'LIMITED'
/* Options are LIMITED, SAMPLED, and DETAILED */
, @onlineRebuild bit = 0
/* 1 = online rebuild; 0 = offline rebuild; only in Enterprise Edition */
, @resumableRebuild bit = 0
/* 1 = resumable rebuild; 0 = normal rebuild */
, @sortInTempDB bit = 0
/* 1 = perform sort operation in TempDB; 0 = perform sort operation in the indexes database */
, @maxDopRestriction tinyint = NULL
/* Option to restrict the number of processors for both index and statistics operations; only in Enterprise Edition */
, @updateStats bit = 1
/* 1 = updates stats when reorganizing; 0 = does not update stats when reorganizing */
, @updateStatsWhere bit = 0
/* 1 = updates only index related stats; 0 = updates all stats in table */
, @statsSample NVARCHAR(8) = NULL
/* Valid options are: NULL, <percentage>, FULLSCAN, and RESAMPLE */
, @persistStatsSample bit = NULL
/* 1 = turns ON fixed sampling rate; 0 = turns OFF fixed sampling rate; NULL = do nothing */
, @statsThreshold float = NULL
/* Valid options are: NULL to use default stats sample method (same as TF2371), float number greater or equal to 0.001 and less than 100 to use custom stats sample */
, @statsMinRows bigint = NULL
/* Valid options are: NULL , integer number that sets the min number of rows a table has to have to be considered for @statsThreshold use */
, @ix_statsnorecompute bit = 0
/* 1 = STATISTICS_NORECOMPUTE on; 0 = default which is with STATISTICS_NORECOMPUTE off */
, @statsIncremental bit = NULL
/* NULL = Keep server setting; 1 = Enable auto create statistics with Incremental; 0 = Disable auto create statistics with Incremental */
, @dealMaxPartition bit = 0
/* 0 = only right-most partition; 1 = exclude right-most populated partition; NULL = do not exclude; see notes for caveats; only in Enterprise Edition */
, @dealLOB bit = 0
/* 0 = compact LOBs when reorganizing (default behavior); 1 = does not compact LOBs when reorganizing */
, @ignoreDropObj bit = 0
/* 0 = includes errors about objects that have been dropped since the defrag cycle began (default behavior);
1 = for error reporting purposes, ignores the fact that objects have been dropped since the defrag cycle began */
, @disableNCIX bit = 0
/* 0 = does NOT disable non-clustered indexes prior to a rebuild;
1 = disables non-clustered indexes prior to a rebuild, if the database is not being replicated (space saving feature) */
, @offlinelocktimeout int = -1
/* -1 = (default) indicates no time-out period; Any other positive integer sets the number of milliseconds that will pass before Microsoft SQL Server returns a locking error */
, @onlinelocktimeout int = 5
/* 5 = (default) indicates a time-out period for locks to wait at low priority, expressed in minutes; this is valid from SQL Server 2014 onwards */
, @abortAfterwait bit = 1
/* NULL = (default) After lock timeout occurs, continue waiting for the lock with normal (regular) priority;
0 = Kill all user transactions that block the online index rebuild DDL operation so that the operation can continue.
1 = Exit the online index rebuild DDL operation currently being executed without taking any action.*/
, @dealROWG bit = 0
/* 0 = (default) compress closed rowgroups on columnstore.
1 = compress all rowgroups on columnstore, and not just closed ones.*/
, @getBlobfrag bit = 0
/* 0 = (default) exclude blobs from fragmentation scan.
1 = include blobs and off-row data when scanning for fragmentation.*/
, @dataCompression NVARCHAR(4) = NULL
/* NULL = (default) Keeps whatever compression setting exists for the object and partition;
NONE = Forces no compression (and removes compression if set);
PAGE = Forces page compression;
ROW = Forces row compression.*/
AS
/*
usp_AdaptiveIndexDefrag.sql - pedro.lopes@microsoft.com (http://aka.ms/AID)
v1 inspired by Michelle Ufford (http://sqlfool.com)
PURPOSE: Intelligent defrag on one or more indexes and statistics update for one or more databases.
DISCLAIMER:
This code is not supported under any Microsoft standard support program or service.
This code and information are provided "AS IS" without warranty of any kind, either expressed or implied.
The entire risk arising out of the use or performance of the script and documentation remains with you.
Furthermore, Microsoft, the author or "Blog do Ezequiel" team shall not be liable for any damages you may sustain by using this information, whether direct,
indirect, special, incidental or consequential, including, without limitation, damages for loss of business profits, business interruption, loss of business information
or other pecuniary loss even if it has been advised of the possibility of such damages.
Read all the implementation and usage notes thoroughly.
CHANGE LOG:
See https://github.com/microsoft/tigertoolbox/blob/master/AdaptiveIndexDefrag/CHANGELOG.txt
IMPORTANT:
Execute in the database context of where you created the log and working tables.
ALL parameters are optional. If not specified, the defaults for each parameter are used.
@Exec_Print 1 = execute the SQL code generated by this SP;
0 = print commands only
@printCmds 1 = print commands to screen;
0 = do not print commands
@outputResults 1 = output fragmentation information after run completes;
0 = do not output fragmentation information
@debugMode 1 = display debug comments;
0 = do not display debug comments
@timeLimit Limits how much time can be spent performing index defrags; expressed in minutes.
NOTE: The time limit is checked BEFORE an index defrag begins, thus a long index defrag can exceed the time limit.
@dbScope Specify specific database name to defrag; if not specified, all non-system databases plus msdb and model will be defragmented.
@tblName Specify if you only want to defrag indexes for a specific table, format = schema.table_name; if not specified, all tables will be defragmented.
@defragOrderColumn Defines how to prioritize the order of defrags. Only used if @Exec_Print = 1.
range_scan_count = count of range and table scans on the index; this is what can benefit the most from defragmentation;
fragmentation = amount of fragmentation in the index;
page_count = number of pages in the index; bigger indexes can take longer to defrag and thus generate more contention; may want to start with these;
@defragSortOrder The sort order of the ORDER BY clause on the above query on how to prioritize the order of defrags.
ASC (ascending)
DESC (descending) is the default.
@forceRescan Action on index rescan. If = 0, a rescan will not occur until all indexes have been defragmented. This can span multiple executions.
1 = force a rescan
0 = use previous scan, if there are indexes left to defrag
@defragDelay Time to wait between defrag commands; gives the server a breathe between runs
@ixtypeOption NULL = all indexes will be defragmented;
1 = only Clustered indexes will be defragmented;
0 = only Non-Clustered indexes will be defragmented (includes XML and Spatial Indexes);
@minFragmentation Defaults to 5%, will not defrag if fragmentation is less.
Refer to http://docs.microsoft.com/sql/relational-databases/indexes/reorganize-and-rebuild-indexes
@rebuildThreshold Defaults to 30%. greater than 30% will result in rebuild instead of reorganize.
Refer to http://docs.microsoft.com/sql/relational-databases/indexes/reorganize-and-rebuild-indexes
@rebuildThreshold_csDefaults to 10%. Greater than 10% will result in columnstore rebuild.
Refer to http://docs.microsoft.com/sql/relational-databases/sql-server-index-design-guide
@minPageCount Specifies how many pages must exist in an index in order to be considered for a defrag. Default to an extent.
Refer to http://docs.microsoft.com/sql/relational-databases/indexes/reorganize-and-rebuild-indexes
NOTE: The @minPageCount will restrict the indexes that are stored in tbl_AdaptiveIndexDefrag_Working table and can render other options inoperative.
@maxPageCount Specifies the maximum number of pages that can exist in an index and still be considered for a defrag.
Useful for scheduling small indexes during business hours and large indexes for non-business hours.
NOTE: The @maxPageCount will restrict the indexes selective for defrag;
@fillfactor 1 = original from when the index was created or last defragmented;
0 = default fill factor
@scanMode Specifies which scan mode to use to determine fragmentation levels. Options are:
LIMITED = the fastest mode and scans the smallest number of pages.
For an index, only the parent-level pages of the B-tree (that is, the pages above the leaf level) are scanned.
For a heap, only the associated PFS and IAM pages are examined; the data pages of the heap are not scanned.
Recommended for most cases.
SAMPLED = returns statistics based on a 1 percent sample of all the pages in the index or heap.
If the index or heap has fewer than 10,000 pages, DETAILED mode is used instead of SAMPLED.
DETAILED = scans all pages and returns all statistics. Can cause performance issues.
@onlineRebuild 1 = online rebuild if possible; only in Enterprise Edition;
0 = offline rebuild
@sortInTempDB When 1, the sort results are stored in TempDB. When 0, the sort results are stored in the filegroup or partition scheme in which the resulting index is stored.
If a sort operation is not required, or if the sort can be performed in memory, SORT_IN_TEMPDB is ignored.
Enabling this option can result in faster defrags and prevent database file size inflation. Just have monitor TempDB closely.
Refer to http://docs.microsoft.com/sql/relational-databases/indexes/sort-in-tempdb-option-for-indexes and http://docs.microsoft.com/sql/relational-databases/indexes/disk-space-requirements-for-index-ddl-operations and http://docs.microsoft.com/sql/relational-databases/indexes/disk-space-requirements-for-index-ddl-operations
1 = perform sort operation in TempDB
0 = perform sort operation in the indexes database
@maxDopRestriction Option to specify a processor limit for index rebuilds and statistics updates. If not specified, defrag operations will use the system MaxDOP setting, up to a limit of 8.
@updateStats 1 = updates stats when reorganizing;
0 = does not update stats when reorganizing
@updateStatsWhere Update statistics within certain thresholds. Refer to http://docs.microsoft.com/sql/relational-databases/statistics/statistics
1 = updates only index related stats;
0 = updates all stats in entire table
@statsSample NULL = perform a sample scan on the target table or indexed view. The database engine automatically computes the required sample size;
<percentage> = perform a fixed percentage scan on the target table or indexed view. Valid values are integers between 1 and 100;
FULLSCAN = all rows in table or view should be read to gather the statistics;
RESAMPLE = statistics will be gathered using an inherited sampling ratio for all existing statistics including indexes
@persistStatsSample Persist a specific statistics sampling rate (depends on version) if <percentage> or FULLSCAN needs to be used in @statsSample
NULL = do nothing;
1 = turns ON fixed sampling rate;
0 = turns OFF fixed sampling rate
@statsThreshold Custom threshold of changes needed to trigger update statistics, overriding default handling;
NULL = assume default handling which is similar to TF2371;
A float number greater or equal to 0.001 and less than 100 to use custom stats sample
@statsMinRows Sets the min number of rows a table has to have to be considered for @statsThreshold use;
NULL = use @statsThreshold (if set) for any size table;
An integer number that sets the min number of rows a table has to have to be considered for @statsThreshold use
@ix_statsnorecompute 1 = STATISTICS_NORECOMPUTE on will disable the auto update statistics.
If you are dealing with stats update with a custom job (or even with this code by updating statistics), you may use this option;
0 = default which is with STATISTICS_NORECOMPUTE off
@statsIncremental When Incremental is ON, the statistics created are per partition statistics.
When OFF, the statistics tree is dropped and SQL Server re-computes the statistics. This setting overrides the database level INCREMENTAL property.
Refer to http://docs.microsoft.com/sql/relational-databases/statistics/statistics
NULL = Keep server setting;
1 = Enable auto create statistics with Incremental
0 = Disable auto create statistics with Incremental
@dealMaxPartition If an index is partitioned, this option specifies whether to exclude the right-most populated partition, or act only on that same partition, excluding all others.
Typically, this is the partition that is currently being written to in a sliding-window scenario.
Enabling this feature may reduce contention. This may not be applicable in other types of partitioning scenarios.
Non-partitioned indexes are unaffected by this option. Only in Enterprise Edition.
1 = exclude right-most populated partition
0 = only right-most populated partition (remember to verify @minPageCount, if partition is smaller than @minPageCount, it won't be considered)
NULL = do not exclude any partitions
@dealLOB Specifies that all pages that contain large object (LOB) data are compacted. The LOB data types are image, text, ntext, varchar(max), nvarchar(max), varbinary(max), and xml.
Compacting this data can improve disk space use.
Reorganizing a specified clustered index compacts all LOB columns that are contained in the clustered index.
Reorganizing a non-clustered index compacts all LOB columns that are nonkey (included) columns in the index.
0 = compact LOBs when reorganizing (default behavior);
1 = does not compact LOBs when reorganizing
@ignoreDropObj If a table or index is dropped after the defrag cycle has begun, you can choose to ignore those errors in the overall outcome,
thus not showing a job as failed if the only errors present refer to dropped database objects.
0 = includes errors about objects that have been dropped since the defrag cycle began (default behavior);
1 = for error reporting purposes, ignores the fact that objects have been dropped since the defrag cycle began
@disableNCIX If disk space is limited, it may be helpful to disable the non-clustered index before rebuilding it;
When a non-clustered index is not disabled, the rebuild operation requires enough temporary disk space to store both the old and new index;
However, by disabling and rebuilding a non-clustered index in separate transactions, the disk space made available by disabling the index can be reused by the subsequent rebuild or any other operation;
No additional space is required except for temporary disk space for sorting; this is typically 20 percent of the index size;
Does not disable indexes on partitioned tables when defragging a subset of existing partitions;
Keeps track of whatever indexes were disabled by the defrag cycle. In case the defrag is cancelled, it will account for these disabled indexes in the next run.
0 = does NOT disable non-clustered indexes prior to a rebuild (default behavior);
1 = disables non-clustered indexes prior to a rebuild (space saving feature)
@offlinelocktimeout As set in SET LOCK_TIMEOUT option
Refer to http://docs.microsoft.com/sql/t-sql/statements/set-lock-timeout-transact-sql
-1 = (default) indicates no time-out period
Any other positive integer = sets the number of milliseconds that will pass before Microsoft SQL Server returns a locking error
@onlinelocktimeout Indicates a time-out period for locks to wait at low priority, expressed in minutes; this is valid from SQL Server 2014 onwards
@abortAfterwait If the online low priority lock timeout occurs, this will set the action to perform afterwards.
NULL = (default) After lock timeout occurs, continue waiting for the lock with normal (regular) priority;
1 = Exit the online index rebuild DDL operation currently being executed without taking any action;
2 = Kill all user transactions that block the online index rebuild DDL operation so that the operation can continue.
@dealROWG Set Columnstore reorg option to compress all rowgroups, and not just closed ones
0 = (default) compress closed rowgroups on columnstore.
1 = compress all rowgroups on columnstore, and not just closed ones.
@getBlobfrag Indicates whether to exclude or include blobs from fragmentation scan.
0 = (default) exclude blobs from fragmentation scan.
1 = include blobs and off-row data when scanning for fragmentation.
@dataCompression Sets specific compression options for all indexes.
NULL = (default) Does not use the DATA_COMPRESSION keyword in index rebuild, keeping whatever compression setting exists.
NONE = Index or specified partitions are not compressed.
PAGE = Use page compression.
ROW = Use row compression.
-------------------------------------------------------
Usage:
EXEC dbo.usp_AdaptiveIndexDefrag
or customize it like the example:
EXEC dbo.usp_AdaptiveIndexDefrag
@Exec_Print = 0
, @printCmds = 1
, @updateStats = 1
, @updateStatsWhere = 1
, @debugMode = 1
, @outputResults = 1
, @dbScope = 'AdventureWorks2008R2'
, @forceRescan = 1
, @maxDopRestriction = 2
, @minPageCount = 8
, @maxPageCount = NULL
, @minFragmentation = 1
, @rebuildThreshold = 1
, @rebuildThreshold_cs = 1
, @defragDelay = '00:00:05'
, @defragOrderColumn = 'range_scan_count'
, @dealMaxPartition = NULL
, @disableNCIX = 1
, @offlinelocktimeout = 180;
*/
SET NOCOUNT ON;
SET XACT_ABORT ON;
SET QUOTED_IDENTIFIER ON;
SET DATEFORMAT ymd;
SET DEADLOCK_PRIORITY -10;
-- Required so it can update stats on Indexed Views and Filtered Indexes
SET ANSI_WARNINGS ON;
SET ANSI_PADDING ON;
SET ANSI_NULLS ON;
SET ARITHABORT ON;
SET CONCAT_NULL_YIELDS_NULL ON;
SET NUMERIC_ROUNDABORT OFF;
/* Find sql server version and edition info */
DECLARE @sqlmajorver int, @sqlminorver int, @sqlbuild int, @engineedition int;
SELECT @sqlmajorver = CONVERT(int, (@@microsoftversion / 0x1000000) & 0xff);
SELECT @sqlminorver = CONVERT(int, (@@microsoftversion / 0x10000) & 0xff);
SELECT @sqlbuild = CONVERT(int, @@microsoftversion & 0xffff);
SELECT @engineedition = CONVERT(int, SERVERPROPERTY('EngineEdition'));
BEGIN
BEGIN TRY
/* Validating and normalizing options... */
IF @debugMode = 1
RAISERROR('Validating options...', 0, 42) WITH NOWAIT;
IF @minFragmentation IS NULL OR @minFragmentation NOT BETWEEN 0.00 AND 100.0
SET @minFragmentation = 5.0;
IF @rebuildThreshold IS NULL OR @rebuildThreshold NOT BETWEEN 0.00 AND 100.0
SET @rebuildThreshold = 30.0;
IF @rebuildThreshold_cs IS NULL OR @rebuildThreshold_cs NOT BETWEEN 0.00 AND 100.0
SET @rebuildThreshold_cs = 10.0;
IF @statsThreshold IS NOT NULL AND @statsThreshold NOT BETWEEN 0.001 AND 100.0
SET @statsThreshold = NULL;
IF @timeLimit IS NULL
SET @timeLimit = 480;
IF @dataCompression IS NOT NULL AND UPPER(@dataCompression) NOT IN ('NONE','ROW','PAGE')
SET @dataCompression = NULL;
/* Validate if table name is fully qualified and database scope is set */
IF @tblName IS NOT NULL AND @tblName NOT LIKE '%.%'
BEGIN
RAISERROR('WARNING: Table name must be fully qualified. Input format should be <schema>.<table_name>.', 15, 42) WITH NOWAIT;
RETURN
END;
/* Validate if database scope is set when table name is also set */
IF @tblName IS NOT NULL AND @dbScope IS NULL
BEGIN
RAISERROR('WARNING: A database scope must be set when using table names.', 15, 42) WITH NOWAIT;
RETURN
END;
/* Validate if database scope exists */
IF @dbScope IS NOT NULL AND LOWER(@dbScope) NOT IN (SELECT LOWER([name]) FROM sys.databases WHERE LOWER([name]) NOT IN ('master', 'tempdb', 'model', 'reportservertempdb','semanticsdb') AND is_distributor = 0)
BEGIN
RAISERROR('WARNING: The database in scope does not exist or is a system database.', 15, 42) WITH NOWAIT;
RETURN
END;
/* Validate offline lock timeout settings */
IF @offlinelocktimeout IS NULL OR ISNUMERIC(@offlinelocktimeout) <> 1
BEGIN
RAISERROR('WARNING: Offline lock timeout must be set to an integer number.', 15, 42) WITH NOWAIT;
RETURN
END;
IF @offlinelocktimeout <> -1 AND @offlinelocktimeout IS NOT NULL
SET @offlinelocktimeout = ABS(@offlinelocktimeout)
/* Validate online lock timeout settings */
IF @onlinelocktimeout IS NULL OR ISNUMERIC(@onlinelocktimeout) <> 1
BEGIN
RAISERROR('WARNING: Online lock timeout must be set to an integer number.', 15, 42) WITH NOWAIT;
RETURN
END;
IF @onlinelocktimeout <> 5 AND @onlinelocktimeout IS NOT NULL
SET @onlinelocktimeout = ABS(@onlinelocktimeout)
/* Validate online lock timeout wait action settings */
IF @abortAfterwait IS NOT NULL AND @abortAfterwait NOT IN (0,1)
BEGIN
RAISERROR('WARNING: Online lock timeout action is invalid.', 15, 42) WITH NOWAIT;
RETURN
END;
/* Validate amount of breather time to give between operations*/
IF @defragDelay NOT LIKE '00:[0-5][0-9]:[0-5][0-9]'
BEGIN
SET @defragDelay = '00:00:05';
RAISERROR('Defrag delay input not valid. Defaulting to 5s.', 0, 42) WITH NOWAIT;
END;
IF @defragOrderColumn IS NULL OR LOWER(@defragOrderColumn) NOT IN ('range_scan_count', 'fragmentation', 'page_count')
BEGIN
SET @defragOrderColumn = 'range_scan_count';
RAISERROR('Defrag order input not valid. Defaulting to range_scan_count.', 0, 42) WITH NOWAIT;
END;
IF @defragSortOrder IS NULL OR UPPER(@defragSortOrder) NOT IN ('ASC', 'DESC')
SET @defragSortOrder = 'DESC';
IF UPPER(@scanMode) NOT IN ('LIMITED', 'SAMPLED', 'DETAILED')
BEGIN
SET @scanMode = 'LIMITED';
RAISERROR('Index scan mode input not valid. Defaulting to LIMITED.', 0, 42) WITH NOWAIT;
END;
IF @ixtypeOption IS NOT NULL AND @ixtypeOption NOT IN (0,1)
SET @ixtypeOption = NULL;
IF @statsSample IS NOT NULL AND (ISNUMERIC(@statsSample) = 1 AND @statsSample NOT BETWEEN 1 AND 100)
OR (ISNUMERIC(@statsSample) = 0 AND UPPER(@statsSample) NOT IN ('FULLSCAN', 'RESAMPLE'))
SET @statsSample = NULL
IF (@persistStatsSample IS NOT NULL AND @persistStatsSample NOT IN (0,1))
OR (@sqlmajorver <> 13 OR (@sqlmajorver = 13 AND @sqlbuild < 4446))
SET @persistStatsSample = NULL;
/* Recognize if database in scope is a Always On secondary replica */
IF @dbScope IS NOT NULL AND @sqlmajorver >= 11 AND @engineedition NOT IN (5, 6) -- Except SQL Database and SQL DW
BEGIN
DECLARE @sqlcmdAO NVARCHAR(3000), @paramsAO NVARCHAR(50), @DBinAG int
SET @sqlcmdAO = 'IF LOWER(@dbScopeIN) IN (SELECT LOWER(DB_NAME(dr.database_id))
FROM sys.dm_hadr_database_replica_states dr
INNER JOIN sys.dm_hadr_availability_replica_states rs ON dr.group_id = rs.group_id
INNER JOIN sys.databases d ON dr.database_id = d.database_id
WHERE rs.role = 2 -- Is Secondary
AND dr.is_local = 1
AND rs.is_local = 1)
BEGIN
SET @DBinAG_OUT = 1
END
ELSE
BEGIN
SET @DBinAG_OUT = 0
END'
SET @paramsAO = N'@dbScopeIN NVARCHAR(256), @DBinAG_OUT int OUTPUT'
EXECUTE sp_executesql @sqlcmdAO, @paramsAO, @dbScopeIN = @dbScope, @DBinAG_OUT = @DBinAG OUTPUT
IF @DBinAG = 1
BEGIN
RAISERROR('WARNING: Cannot defrag database in scope because it is part of an Always On secondary replica.', 15, 42) WITH NOWAIT;
RETURN
END
END
/* Check if database scope has changed, if rescan is not being forced */
IF @forceRescan = 0 AND @dbScope IS NOT NULL -- Specific scope was set
BEGIN
IF (SELECT COUNT(DISTINCT [dbID]) FROM dbo.tbl_AdaptiveIndexDefrag_Working) > 1
OR QUOTENAME(LOWER(@dbScope)) NOT IN (SELECT DISTINCT LOWER([dbName]) FROM dbo.tbl_AdaptiveIndexDefrag_Working UNION SELECT DISTINCT LOWER(dbName) FROM dbo.tbl_AdaptiveIndexDefrag_Stats_Working)
BEGIN
SET @forceRescan = 1
RAISERROR('Scope has changed. Forcing rescan of single database in scope...', 0, 42) WITH NOWAIT;
END;
END;
/* Recognize if we have indexes of the chosen type left to defrag or stats left to update;
otherwise force rescan of database(s), if rescan is not being forced */
IF @forceRescan = 0
AND (NOT EXISTS (SELECT TOP 1 * FROM dbo.tbl_AdaptiveIndexDefrag_Working WHERE defragDate IS NULL AND [type] = 1 AND [exclusionMask] & POWER(2, DATEPART(weekday, GETDATE())-1) = 0) AND @ixtypeOption = 1)
AND (NOT EXISTS (SELECT TOP 1 * FROM dbo.tbl_AdaptiveIndexDefrag_Working WHERE defragDate IS NULL AND [type] <> 1 AND [exclusionMask] & POWER(2, DATEPART(weekday, GETDATE())-1) = 0) AND @ixtypeOption = 0)
AND (NOT EXISTS (SELECT TOP 1 * FROM dbo.tbl_AdaptiveIndexDefrag_Working WHERE defragDate IS NULL AND [exclusionMask] & POWER(2, DATEPART(weekday, GETDATE())-1) = 0 ) AND @ixtypeOption IS NULL)
AND NOT EXISTS (SELECT TOP 1 * FROM dbo.tbl_AdaptiveIndexDefrag_Stats_Working AS idss WHERE idss.updateDate IS NULL AND NOT EXISTS (SELECT TOP 1 objectID FROM dbo.tbl_AdaptiveIndexDefrag_Working ids WHERE ids.[dbID] = idss.[dbID] AND ids.objectID = idss.objectID AND idss.statsName = ids.indexName AND idss.updateDate IS NULL AND ids.exclusionMask & POWER(2, DATEPART(weekday, GETDATE())-1) = 0))
BEGIN
SET @forceRescan = 1
RAISERROR('No indexes of the chosen type left to defrag nor statistics left to update. Forcing rescan...', 0, 42) WITH NOWAIT;
END;
/* Recognize if we missing information post-upgrade;
if so force rescan of database(s) */
IF @forceRescan = 0
AND (EXISTS (SELECT TOP 1 * FROM dbo.tbl_AdaptiveIndexDefrag_Working
WHERE (fill_factor IS NULL OR is_padded IS NULL OR compression_type IS NULL) AND [exclusionMask] & POWER(2, DATEPART(weekday, GETDATE())-1) = 0)) BEGIN
SET @forceRescan = 1
RAISERROR('Missing column information due to post-upgrade condition. Forcing rescan...', 0, 42) WITH NOWAIT;
END;
/* Check if any databases where dropped or created since last run, if rescan is not being forced */
IF @forceRescan = 0 AND @dbScope IS NULL
BEGIN
DECLARE @sqlcmd_CntSrc NVARCHAR(3000), @params_CntSrc NVARCHAR(50), @CountSrc int
DECLARE @sqlcmd_CntTgt NVARCHAR(3000), @params_CntTgt NVARCHAR(50), @CountTgt int
DECLARE @dbIDIX int, @dbNameIX sysname, @hasIXs bit, @hasIXsCntsqlcmd NVARCHAR(3000), @hasIXsCntsqlcmdParams NVARCHAR(50)
-- What is in working tables plus exceptions that still exist in server
SET @sqlcmd_CntSrc = 'SELECT @CountSrc_OUT = COUNT(DISTINCT Working.[dbID]) FROM
(SELECT DISTINCT [dbID] FROM [' + DB_NAME() + '].dbo.tbl_AdaptiveIndexDefrag_Working
UNION
SELECT DISTINCT [dbID] FROM [' + DB_NAME() + '].dbo.tbl_AdaptiveIndexDefrag_Stats_Working
UNION
SELECT DISTINCT [dbID] FROM [' + DB_NAME() + '].dbo.tbl_AdaptiveIndexDefrag_Exceptions
WHERE [dbID] IN (SELECT DISTINCT database_id FROM sys.databases sd
WHERE LOWER(sd.[name]) NOT IN (''master'', ''tempdb'', ''model'', ''reportservertempdb'',''semanticsdb'')
AND [state] = 0 -- must be ONLINE
AND is_read_only = 0 -- cannot be READ_ONLY
AND is_distributor = 0)
) Working'
SET @params_CntSrc = N'@CountSrc_OUT int OUTPUT'
-- What exists in current instance, in ONLINE state and READ_WRITE
IF EXISTS (SELECT [object_id] FROM tempdb.sys.objects (NOLOCK) WHERE [object_id] = OBJECT_ID('tempdb.dbo.#tblIndexFindInDatabaseList'))
DROP TABLE #tblIndexFindInDatabaseList;
IF NOT EXISTS (SELECT [object_id] FROM tempdb.sys.objects (NOLOCK) WHERE [object_id] = OBJECT_ID('tempdb.dbo.#tblIndexFindInDatabaseList'))
CREATE TABLE #tblIndexFindInDatabaseList
(
[dbID] int
, dbName sysname
, hasIXs bit NOT NULL
, scanStatus bit NULL
);
/* Retrieve the list of databases to loop, excluding Always On secondary replicas */
SET @sqlcmd_CntTgt = 'SELECT [database_id], name, 0, 0 -- not yet scanned
FROM sys.databases
WHERE LOWER([name]) = ISNULL(LOWER(@dbScopeIN), LOWER([name]))
AND LOWER([name]) NOT IN (''master'', ''tempdb'', ''model'', ''reportservertempdb'',''semanticsdb'') -- exclude system databases
AND [state] = 0 -- must be ONLINE
AND is_read_only = 0 -- cannot be READ_ONLY
AND is_distributor = 0'
IF @sqlmajorver >= 11 AND (SELECT @@VERSION) NOT LIKE 'Microsoft SQL Azure%' -- Except all local Always On secondary replicas
BEGIN
SET @sqlcmd_CntTgt = @sqlcmd_CntTgt + CHAR(10) + 'AND [database_id] NOT IN (SELECT dr.database_id FROM sys.dm_hadr_database_replica_states dr
INNER JOIN sys.dm_hadr_availability_replica_states rs ON dr.group_id = rs.group_id
INNER JOIN sys.databases d ON dr.database_id = d.database_id
WHERE rs.role = 2 -- Is Secondary
AND dr.is_local = 1
AND rs.is_local = 1)'
END
SET @params_CntTgt = N'@dbScopeIN NVARCHAR(256)'
INSERT INTO #tblIndexFindInDatabaseList
EXECUTE sp_executesql @sqlcmd_CntTgt, @params_CntTgt, @dbScopeIN = @dbScope
WHILE (SELECT COUNT(*) FROM #tblIndexFindInDatabaseList WHERE scanStatus = 0) > 0
BEGIN
SELECT TOP 1 @dbNameIX = [dbName] FROM #tblIndexFindInDatabaseList WHERE scanStatus = 0;
SET @dbIDIX = DB_ID(@dbNameIX)
SET @hasIXsCntsqlcmd = 'IF EXISTS (SELECT TOP 1 [index_id] from [' + DB_NAME(@dbIDIX) + '].sys.indexes AS si