index optimize
- 背景介绍:
DBA 经常需要维护索引碎片保证查询效率,在维护索引的同时也会不可避免的造成部分语句的暂时block。出于工作需要,编写了这个简单的索引维护逻辑。
- 环境介绍
sql server 2016 单节点 / alway on
- 维护逻辑介绍
入下图所示:1.生成待维护的索引列表(monitor.dbo.dba_index_fragment)
2.启动job DBA_index_main 作为主进程 调用job DBA_index_optimize_01/02/0* 并行维护索引。可根据需要创建不同数量的job DBA_index_optimize_0* 来调节并行数量缩短index维护时间。
优缺点介绍:可以自动发现跳过长时间block其他session,继续维护队列内的下一个index. 可以根据需要调节并行维护的进程数量。该逻辑仅仅维护index 没有做统计信息更新
创建完成后默认会有3个job入下图所示
- 代码逻辑
- database&log table: Monitor / dba_Log
- procedure: updba_index_fragment / updba_index_fragment_optimize / updba_index_main
- job: DBA_index_optimize_01 / DBA_index_optimize_02 / DBA_index_main
Monitor / dba_Log
USE [master] GO create database monitor go use monitor go sp_changedbowner sa use monitor go /* drop table dba_Log */ create table dbo.dba_Log ( id bigint identity(1,1) primary key ,DatabaseName nvarchar(200) ,Programe varchar(100) ,Command nvarchar(max) ,CommandType varchar(200) ,StartTime datetime ,EndTime datetime ,errorNumber int ,errorMessage nvarchar(max) ,note varchar(200) ) create index ix_StartTime on dbo.dba_Log (StartTime)
procedure updba_index_fragment
USE [Monitor] GO if ( object_id('dba_index_fragment') is not null and not exists(select * from sys.columns with(nolock) where object_id=OBJECT_ID('dba_index_fragment') and name='batch') ) drop table dbo.dba_index_fragment go IF EXISTS (SELECT * FROM monitor.sys.objects WHERE object_id = OBJECT_ID(N'dbo.updba_index_fragment') AND type in (N'P ')) drop procedure dbo.updba_index_fragment go USE [Monitor] GO /* get index fragment informations edtior: lynn lin run demo: exec [dbo].[updba_index_fragment] exec [dbo].updba_index_fragment 'dbname1' exec [dbo].[updba_index_fragment] 'dbname1,dbname2','dbo.tbname1,dbo.tbname2' select * from [dba_index_fragment] with(nolock) truncate table dba_index_fragment */ create proc [dbo].[updba_index_fragment] @db_list nvarchar(max) = NULL --NULL all database , run database demo: N'dbname1,dbname2' ,@tb_list nvarchar(max) = NULL --NULL all table , run table demo: N'dbo.table1,dbo.table2' ,@fragment_mode varchar(10) ='LIMITED' --LIMITED SAMPLED DETAILED ,@SaveDays int=180 as begin set nocount on; IF NOT EXISTS (SELECT * FROM monitor.sys.objects WHERE object_id = OBJECT_ID(N'monitor.dbo.dba_index_fragment') AND type in (N'U')) begin create table dbo.dba_index_fragment ( id int identity(1,1) not null primary key (id) ,batch int ,inTime datetime default getdate() ,isop char(1) default 'n' --y need optimize , other don't need optimize ,optimize_spid int ,optimize_status varchar(15) default null --null, optmizing,canceled,completed ,opStartTime datetime ,opEndTime datetime ,dbName varchar(100) ,schemaName varchar(50) ,tbName varchar(100) ,ixName varchar(500) ,ixType_desc varchar(50) ,isClustered bit ,has_max_col bit ,allow_page_locks bit ,in_row_data_page_count int ,row_count int ,avg_fragmentation_in_percent float ,fragment_count bigint ,page_count bigint ,record_count bigint ,avg_record_size_in_bytes float ,user_seeks bigint ,user_scans bigint ,user_lookups bigint ,user_updates bigint ,last_user_seek datetime ,last_user_scan datetime ,last_user_lookup datetime ,last_user_update datetime ,sqlcommand nvarchar(4000) ,comments nvarchar(max) ,dbId int ,tbId int ,ixId int ,ixType tinyint ,[DatabaseSpaceID] int ,[FileGroup] nvarchar(50) ,[DatabaseFileName] nvarchar(500) ) create index ix_intime on dba_index_fragment(intime) end else BEGIN delete monitor.dbo.dba_index_fragment where intime<dateadd(day,-(ABS(@SaveDays)),getdate()) --delete history data。 --delete monitor.dbo.dba_index_fragment where isop ='n' END --clear history data and useless data delete [monitor].[dbo].[dba_Log] where programe='updba_index_fragment' and StartTime<dateadd(day,-(ABS(@SaveDays)),getdate()) --delete history data。 declare @batch int select @batch=max(batch) from monitor.dbo.dba_index_fragment with(nolock) if @batch is null set @batch=1 else set @batch=@batch+1 ----fillter begin ****************************** if object_id('tempdb.dbo.#dblist','U') is not null drop table #dblist create table #dblist (db varchar(400)) if object_id('tempdb.dbo.#tblist','U') is not null drop table #tblist create table #tblist ( db varchar(400) ,sche varchar(100) ,tb varchar(500) ) --filter db insert into #dblist(db) select T.name from( SELECT name = PARSENAME( LTRIM(RTRIM(T.c.value('.[1]', 'sysname'))), 1 ) FROM( SELECT database_name_list = CONVERT(xml, N'<c><![CDATA[' + REPLACE( REPLACE( REPLACE(@db_list,CHAR(13),CHAR(10) ), CHAR(10),N',' ), N',',N']]></c><c><![CDATA[' ) + N']]></c>' ) )REQ CROSS APPLY REQ.database_name_list.nodes('/c/text()') T(c) ) T inner join sys.databases d on T.name=d.name and d.state=0 WHERE T.name > N'' --filter tb insert into #tblist(db,sche,tb) SELECT db = ISNULL(PARSENAME(name, 3), N'*'), sche = ISNULL(PARSENAME(name, 2), N'dbo'), tb = PARSENAME(name, 1) from( SELECT name = LTRIM(RTRIM(T.c.value('.[1]', 'sysname'))) FROM( SELECT table_name_list = CONVERT(xml, N'<c><![CDATA[' + REPLACE( REPLACE( REPLACE( REPLACE( @tb_list, CHAR(9), N'.' ), CHAR(13), CHAR(10) ), CHAR(10), N',' ), N',', N']]></c><c><![CDATA[' ) + N']]></c>' ) )REQ CROSS APPLY REQ.table_name_list.nodes('/c/text()') T(c) )T WHERE name > N'' --fillter end ****************************** if object_id('tempdb.dbo.#dba_index_fragment','U') is not null drop table #dba_index_fragment create table #dba_index_fragment ( batch int ,inTime datetime default getdate() ,isop char(1) ,optimize_spid int ,optimize_status varchar(15) default null --null, optmizing,canceled,completed ,opStartTime datetime ,opEndTime datetime ,dbName varchar(100) ,schemaName varchar(50) ,tbName varchar(100) ,ixName varchar(500) ,ixType_desc varchar(50) ,isClustered bit ,has_max_col bit ,allow_page_locks bit ,in_row_data_page_count int ,row_count int ,avg_fragmentation_in_percent float ,fragment_count bigint ,page_count bigint ,record_count bigint ,avg_record_size_in_bytes float ,user_seeks bigint ,user_scans bigint ,user_lookups bigint ,user_updates bigint ,last_user_seek datetime ,last_user_scan datetime ,last_user_lookup datetime ,last_user_update datetime ,sqlcommand nvarchar(4000) ,comments nvarchar(max) ,dbId int ,tbId int ,ixId int ,ixType tinyint ,[DatabaseSpaceID] int ,[FileGroup] nvarchar(50) ,[DatabaseFileName] nvarchar(500) ) declare @sql_index nvarchar(max) select @sql_index=N' ;with COLMAX AS( SELECT C.object_id FROM sys.columns C WITH(NOLOCK) inner join sys.types T WITH(NOLOCK) on C.user_type_id = T.user_type_id WHERE( C.user_type_id IN(34, 35, 99) OR( C.user_type_id IN(165, 167, 231, 241) AND C.max_length = -1 ) ) ) insert into #dba_index_fragment(batch,dbId,tbId,ixId,ixType,dbName,schemaName,tbName,ixName,ixType_desc,has_max_col, [allow_page_locks],in_row_data_page_count,row_count,DatabaseSpaceID,[FileGroup],[DatabaseFileName],isClustered ,user_seeks,user_scans,user_lookups,user_updates ,last_user_seek,last_user_scan,last_user_lookup,last_user_update,inTime,isop) SELECT distinct @batch, dbId = db_id(), tbId=t.object_id, ixId=i.index_id, ixType = i.type ,dbName=db_name(), schemaName=sc.name, tb=t.name, ixName = i.name, ixType_desc = i.type_desc ,has_max_col=CASE WHEN EXISTS( SELECT * FROM COLMAX WHERE object_id = t.object_id ) THEN 1 ELSE 0 END ,i.allow_page_locks,p.in_row_data_page_count,p.row_count ,i.[data_space_id] AS [DatabaseSpaceID],f.[name] AS [FileGroup] ,d.[physical_name] AS [DatabaseFileName] ,isClustered=case when i.type in ( 1,5 ) then 1 else 0 end --index used info ,ixu.user_seeks,ixu.user_scans,ixu.user_lookups,ixu.user_updates ,ixu.last_user_seek,ixu.last_user_scan,ixu.last_user_lookup,ixu.last_user_update,inTime = getdate() ,''n'' FROM sys.tables t inner join sys.schemas sc with(nolock) on t.schema_id=sc.schema_id inner join [sys].[indexes] i on i.object_id=t.object_id INNER JOIN [sys].[filegroups] f ON f.[data_space_id] = i.[data_space_id] INNER JOIN [sys].[database_files] d ON f.[data_space_id] = d.[data_space_id] INNER JOIN [sys].[data_spaces] s ON f.[data_space_id] = s.[data_space_id] inner join sys.dm_db_partition_stats p WITH(nolock) on p.object_id=i.object_id and p.index_id=i.index_id left join sys.dm_db_index_usage_stats ixu with (nolock) on i.object_id=ixu.object_id and i.index_id=ixu.index_id WHERE t.is_ms_shipped=0 and i.[type_desc] not in ( ''HEAP'', ''CLUSTERED COLUMNSTORE'') and p.row_count >10000 and p.in_row_data_page_count>128 and ( ( (ixu.user_seeks>100 or ixu.user_scans>100 or ixu.user_lookups>100) and ( ixu.last_user_seek>dateadd(DAY,-30,getdate()) or ixu.last_user_scan >dateadd(DAY,-30,getdate()) or ixu.last_user_lookup >dateadd(DAY,-30,getdate()) or ixu.last_user_update >dateadd(DAY,-30,getdate()) ) ) or ixu.object_id is null ) ORDER BY t.object_id,i.index_id ,f.[name] ,i.[data_space_id] if exists(select * from #tblist) begin delete f from #dba_index_fragment f left join #tblist t ON t.sche=f.schemaName and t.tb=f.tbName where t.tb is null end declare @dbID int, @tbID int, @ixID int DECLARE CUR_tb CURSOR LOCAL STATIC FORWARD_ONLY READ_ONLY FOR SELECT dbID ,tbId, ixId FROM #dba_index_fragment with(nolock) where dbid=db_id() order by dbID ,tbId, ixId OPEN CUR_tb; FETCH CUR_tb INTO @dbID, @tbID, @ixID WHILE @@FETCH_STATUS = 0 BEGIN ;WITH FRG AS( SELECT * FROM sys.dm_db_index_physical_stats(@dbID, @tbID, @ixID, NULL, @fragment_mode) --WHERE avg_fragmentation_in_percent > 30.0 and index_id > 0 and page_count>8 ) update f set page_count=FRG.page_count ,avg_fragmentation_in_percent=FRG.avg_fragmentation_in_percent ,fragment_count=FRG.fragment_count ,record_count=FRG.record_count ,avg_record_size_in_bytes=FRG.avg_record_size_in_bytes --,isop=''y'' from #dba_index_fragment f with(nolock) inner join FRG on FRG.database_id=f.dbID and FRG.object_id=f.tbID and FRG.index_id=f.ixID update #dba_index_fragment set isop=''y'' where avg_fragmentation_in_percent > 15.0 and ixId > 0 and page_count>8 FETCH CUR_tb INTO @dbID, @tbID, @ixID END CLOSE CUR_tb; DEALLOCATE CUR_tb; ' if object_id('tempdb.dbo.#rundblist','U') is not null drop table #rundblist create table #rundblist (db varchar(400)) if exists (select * from #dblist) begin INSERT INTO #rundblist SELECT distinct db from #dblist end else insert into #rundblist SELECT name FROM sys.databases DB WITH(NOLOCK) WHERE name NOT IN('master', 'tempdb', 'model', 'msdb', 'distribution','monitor' ) and state =0 --online --delete secondary db for always on ;with SECONDARY_DB as ( select adc.database_name as db /* ag.name GroupName,adc.database_name,rs.role_desc,cs.replica_server_name, ds.synchronization_state,ds.synchronization_state_desc, --0 = NOT SYNCHRONIZING / 1 = SYNCHRONIZING / 2 = SYNCHRONIZED / 3 = REVERTING / 4 = INITIALIZING ds.synchronization_health,ds.synchronization_health_desc, --0 = NOT_HEALTHY / 1 = PARTIALLY_HEALTHY / 2 = HEALTHY ar.availability_mode,ar.availability_mode_desc, ar.failover_mode,ar.failover_mode_desc */ from sys.availability_groups ag inner join sys.availability_replicas ar on ag.group_id=ar.group_id inner join sys.availability_databases_cluster adc on adc.group_id=ag.group_id inner join sys.dm_hadr_availability_replica_states rs on rs.group_id=ag.group_id and rs.replica_id=ar.replica_id inner join sys.dm_hadr_availability_replica_cluster_states cs on cs.group_id=ag.group_id and cs.replica_id=ar.replica_id inner join sys.dm_hadr_database_replica_states ds on ds.group_id=ag.group_id and ds.replica_id=ar.replica_id and ds.group_database_id=adc.group_database_id where role_desc='SECONDARY' and cs.replica_server_name=@@SERVERNAME ) delete #rundblist where db in ( select * from SECONDARY_DB ) DECLARE CUR_DB CURSOR LOCAL READ_ONLY FORWARD_ONLY STATIC FOR SELECT db FROM #rundblist DECLARE @sql nvarchar(max), @database_name sysname; OPEN CUR_DB; FETCH CUR_DB INTO @database_name WHILE @@FETCH_STATUS = 0 BEGIN; BEGIN TRY --log begin insert into monitor.dbo.dba_Log (DatabaseName,Programe ,CommandType ,StartTime ) select @database_name,'dba_index_fragment','index fragment',getdate() declare @identity bigint select @identity = null select @identity = SCOPE_IDENTITY() truncate table #dba_index_fragment SET @sql = N'USE ' + QUOTENAME(@database_name)+char(13)+ @sql_index EXEC sp_executesql @sql ,N'@fragment_mode sysname,@batch int' ,@fragment_mode,@batch insert into monitor.dbo.dba_index_fragment select * from #dba_index_fragment where isop='y' order by dbID,tbId,isClustered DESC,page_count --log end update monitor.dbo.dba_Log set EndTime=getdate() where id=@identity END TRY BEGIN CATCH --log begin and error update monitor.dbo.dba_Log set EndTime=getdate() ,errorNumber=ERROR_NUMBER() ,errorMessage=ERROR_MESSAGE() where id=@identity continue END CATCH; FETCH CUR_DB INTO @database_name END; CLOSE CUR_DB; DEALLOCATE CUR_DB; --BEGIN get optimize index scripts declare @isEdition int,@isOnline nvarchar(200)=' ' select @isEdition=CASE WHEN CONVERT(sysname, SERVERPROPERTY(N'Edition')) LIKE N'Enterprise%' THEN 1 ELSE 0 END if @isEdition=1 begin SET @isOnline=N',ONLINE=ON' end declare @cpu_count int,@ncpu int select @cpu_count=cpu_count from sys.dm_os_sys_info with(nolock) select @ncpu= case when @cpu_count<=4 then 1 when @cpu_count<=8 then 2 ELSE 4 end --table have column index begin #################################### --* reorganize fragmentation<=30 and allow_page_locks=1 update dba_index_fragment set sqlcommand= ' use '+QUOTENAME(dbName)+ ' alter index ' + QUOTENAME(ixName) + ' on ' + QUOTENAME(schemaName) + '.' + QUOTENAME(tbName) + ' reorganize ' where batch=@batch and isop='y' and sqlcommand is null and ixType=5 and avg_fragmentation_in_percent<=30.0 and allow_page_locks=1 --* offline rebuild fragmentation>30 update dba_index_fragment set sqlcommand= ' use '+QUOTENAME(dbName)+ ' alter index ' + QUOTENAME(ixName) + ' on ' + QUOTENAME(schemaName) + '.' + QUOTENAME(tbName) + replace(' rebuild with(MAXDOP={@ncpu})','{@ncpu}',@ncpu) where batch=@batch and isop='y' and sqlcommand is null and ixType=5 --if table has colummn index, all indexs can't rebuild online update upt set sqlcommand= ' use '+QUOTENAME(upt.dbName)+ ' alter index ' + QUOTENAME(upt.ixName) + ' on ' + QUOTENAME(upt.schemaName) + '.' + QUOTENAME(upt.tbName) + replace(' rebuild with(MAXDOP={@ncpu})','{@ncpu}',@ncpu) from dba_index_fragment upt join ( select distinct dbId,schemaName,tbId from monitor.dbo.dba_index_fragment where batch=@batch and ixtype=5 ) tc on tc.dbId=upt.dbId and tc.schemaName=upt.schemaName and tc.tbId=upt.tbId where upt.batch=@batch and upt.isop='y' and upt.sqlcommand is null --table have column index end ####################################### --row index begin #################################################### --* reorganize, fragmentation<=30 and allow_page_locks=1 update dba_index_fragment set sqlcommand= ' use '+QUOTENAME(dbName)+ ' alter index ' + QUOTENAME(ixName) + ' on ' + QUOTENAME(schemaName) + '.' + QUOTENAME(tbName) + ' reorganize ' where batch=@batch and isop='y' and sqlcommand is null and ixType<>5 and avg_fragmentation_in_percent<=30.0 and allow_page_locks=1 --* rebuild offline , if table have text、ntext、image or FILESTREAM, clustered index can't use rebuild online update dba_index_fragment set sqlcommand= ' use '+QUOTENAME(dbName)+ ' alter index ' + QUOTENAME(ixName) + ' on ' + QUOTENAME(schemaName) + '.' + QUOTENAME(tbName) + replace(' rebuild with(FILLFACTOR = 100,MAXDOP={@ncpu})','{@ncpu}',@ncpu) where batch=@batch and isop='y' and sqlcommand is null and ixType<>5 and isClustered=1 and has_max_col=1 --* rebuild online update dba_index_fragment set sqlcommand= ' use '+QUOTENAME(dbName)+ ' alter index ' + QUOTENAME(ixName) + ' on ' + QUOTENAME(schemaName) + '.' + QUOTENAME(tbName) + ' rebuild with(FILLFACTOR = 100'+@isOnline+replace(',MAXDOP={@ncpu})','{@ncpu}',@ncpu) where batch=@batch and isop='y' and sqlcommand is null and ixType<>5 --row index end ######################################################### delete [monitor].[dbo].dba_index_fragment where sqlcommand is null end GO
procedure updba_index_fragment_optimize
USE [Monitor] GO IF EXISTS (SELECT * FROM monitor.sys.objects WHERE object_id = OBJECT_ID(N'dbo.updba_index_fragment_optimize') AND type in (N'P ')) drop procedure dbo.updba_index_fragment_optimize go USE [Monitor] GO /* get index optimize command from table monitor.dbo.[dba_index_fragment] , editor: lynn lin exec [updba_index_fragment_optimize] select * from [dba_index_fragment] with(nolock) */ create proc [dbo].[updba_index_fragment_optimize] as begin set nocount on declare @sql nvarchar(4000),@id bigint, @batch int select @batch=max(batch) from monitor.dbo.[dba_index_fragment] with(nolock) while 1=1 begin select @sql = null, @id = null begin tran select top(1) @id=id,@sql=sqlcommand from monitor.dbo.[dba_index_fragment] a with(TABLOCKX) --with(rowlock,xlock,readpast) with(TABLOCKX) where batch=@batch and isop='y' and optimize_spid is null and optimize_status is null and not exists( select top(1) 1 from monitor.dbo.[dba_index_fragment] b with(nolock) where b.batch=@batch and b.optimize_status='optimizing' and b.dbName=a.dbName and b.schemaName=a.schemaName and b.tbName=a.tbName ) if @id is not null begin update monitor.dbo.dba_index_fragment set optimize_status='optmizing' ,opStartTime=getdate() ,optimize_spid=@@SPID where id=@id commit tran end else begin commit tran break end BEGIN TRY exec sp_executesql @sql update monitor.dbo.dba_index_fragment set optimize_status='completed' ,opEndTime=getdate() ,optimize_spid=-1 where id=@id END TRY BEGIN CATCH update monitor.dbo.dba_index_fragment set optimize_status='error' ,comments = ERROR_MESSAGE() ,opEndTime=getdate() ,optimize_spid=-1 where id=@id END CATCH; end end GO
procedure updba_index_main
USE [Monitor] GO IF EXISTS (SELECT * FROM monitor.sys.objects WHERE object_id = OBJECT_ID(N'dbo.updba_index_main') AND type in (N'P ')) drop procedure dbo.updba_index_main go /**************************************************/ use monitor go /* run procedure [updba_index_fragment] to optimize indexes editor: lynn lin exec [updba_index_main] select * from [dba_index_fragment] with(nolock) */ create procedure updba_index_main @begin time='00:00:10' --Maintenance start time ,@end time ='23:59:10' --Maintenance end time as BEGIN --updba_index_main declare --@begin time='01:30:00' --Maintenance start time --@end time ='23:30:00' --Maintenance end time @blocked_seconds int =120 --blocked fillter IF blocked time>@blocked_seconds will skip to next ,@batch int ,@jobname sysname ,@id int --get fragment exec [dbo].[updba_index_fragment] --get index optimize job list declare @startjob table( jobname sysname,flag int) insert into @startjob(jobname) select name from msdb.dbo.sysjobs where name like 'DBA_index_optimize_0%' --start index optimize job while 1=1 begin select @jobname= null select top(1) @jobname=jobname from @startjob where flag is null if @jobname is null break EXEC [msdb].dbo.sp_start_job @job_name = @jobname ; waitfor delay '00:00:05' update @startjob set flag=1 where jobname=@jobname end --get curently batch select @batch=max(batch) from monitor.dbo.[dba_index_fragment] with(nolock) /*store job running status*/ declare @job table ( jobname sysname, spid int, id int, -- column id in table monitor.dbo.[dba_index_fragment] flag int ) while 1=1 begin delete @job /*get cunrrent running job spid*/ ;with jobinfo as( select j.name as jobname,B.text,p.spid from sys.sysprocesses p inner join msdb.dbo.sysjobs j on p.program_name like '%'+replace( right(cast(j.job_id as varchar(50)),17),'-','') +'%' CROSS APPLY sys.dm_exec_sql_text(p.sql_handle) AS B where p.program_name like 'SQLAgent - TSQL JobStep%' and j.name like 'DBA_index_optimize_0%' ) /*just get optimize index spid*/ insert into @job(jobname,spid,id) select jobinfo.jobname,jobinfo.spid,f.id from jobinfo inner join monitor.dbo.[dba_index_fragment] f with(nolock) on jobinfo.spid=f.optimize_spid --and jobinfo.text=f.sqlcommand where f.batch=@batch --if not index oprimize job running will stop if not exists( select top(1) 1 from @job ) break --if out of Maintenance windows stop if cast(getdate() as time) not between @begin and @end begin while 1=1 begin SELECT @jobname=null, @id=null SELECT top(1) @jobname=jobname,@id=id from @job where flag is null if @jobname is null break EXEC [msdb].dbo.sp_stop_job @job_name = @jobname ; update monitor.dbo.[dba_index_fragment] set optimize_spid=-1, optimize_status='cancled', opEndTime=getdate() where id=@id update @job set flag=1 where jobname=@jobname end break end /*get block job*/ ;with blockespid as( select distinct blocked from sys.sysprocesses with(nolock) where blocked<>0 and spid<>blocked and spid>50 and blocked>50 and waittime>(1000*@blocked_seconds) ) delete @job where spid not in ( select blocked from blockespid ) while 1=1 begin set @jobname=null set @id=null select top(1) @jobname=jobname,@id=id from @job where flag is null if @jobname is null break EXEC [msdb].dbo.sp_stop_job @job_name = @jobname ; update monitor.dbo.[dba_index_fragment] set optimize_spid=-1, optimize_status='cancled', opEndTime=getdate() where id=@id waitfor delay '00:00:10' EXEC [msdb].dbo.sp_start_job @job_name = @jobname ; update @job set flag=1 where jobname=@jobname end waitfor delay '00:02:00' end END /*****************************************************/
job DBA_index_optimize_01
USE [msdb] GO /****** Object: Job [DBA_index_optimize_01] Script Date: 11/9/2020 2:34:11 PM ******/ BEGIN TRANSACTION DECLARE @ReturnCode INT SELECT @ReturnCode = 0 /****** Object: JobCategory [[Uncategorized (Local)]] Script Date: 11/9/2020 2:34:11 PM ******/ IF NOT EXISTS (SELECT name FROM msdb.dbo.syscategories WHERE name=N'[Uncategorized (Local)]' AND category_class=1) BEGIN EXEC @ReturnCode = msdb.dbo.sp_add_category @class=N'JOB', @type=N'LOCAL', @name=N'[Uncategorized (Local)]' IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback END DECLARE @jobId BINARY(16) EXEC @ReturnCode = msdb.dbo.sp_add_job @job_name=N'DBA_index_optimize_01', @enabled=1, @notify_level_eventlog=0, @notify_level_email=0, @notify_level_netsend=0, @notify_level_page=0, @delete_level=0, @description=N'No description available.', @category_name=N'[Uncategorized (Local)]', @owner_login_name=N'sa', @job_id = @jobId OUTPUT IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback /****** Object: Step [DBA_index_optimize_01] Script Date: 11/9/2020 2:34:11 PM ******/ EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @job_id=@jobId, @step_name=N'DBA_index_optimize_01', @step_id=1, @cmdexec_success_code=0, @on_success_action=1, @on_success_step_id=0, @on_fail_action=2, @on_fail_step_id=0, @retry_attempts=0, @retry_interval=0, @os_run_priority=0, @subsystem=N'TSQL', @command=N'exec dbo.[updba_index_fragment_optimize]', @database_name=N'Monitor', @flags=0 IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback EXEC @ReturnCode = msdb.dbo.sp_update_job @job_id = @jobId, @start_step_id = 1 IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback EXEC @ReturnCode = msdb.dbo.sp_add_jobserver @job_id = @jobId, @server_name = N'(local)' IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback COMMIT TRANSACTION GOTO EndSave QuitWithRollback: IF (@@TRANCOUNT > 0) ROLLBACK TRANSACTION EndSave: GO
job DBA_index_optimize_02
USE [msdb] GO /****** Object: Job [DBA_index_optimize_02] Script Date: 11/9/2020 2:34:44 PM ******/ BEGIN TRANSACTION DECLARE @ReturnCode INT SELECT @ReturnCode = 0 /****** Object: JobCategory [[Uncategorized (Local)]] Script Date: 11/9/2020 2:34:44 PM ******/ IF NOT EXISTS (SELECT name FROM msdb.dbo.syscategories WHERE name=N'[Uncategorized (Local)]' AND category_class=1) BEGIN EXEC @ReturnCode = msdb.dbo.sp_add_category @class=N'JOB', @type=N'LOCAL', @name=N'[Uncategorized (Local)]' IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback END DECLARE @jobId BINARY(16) EXEC @ReturnCode = msdb.dbo.sp_add_job @job_name=N'DBA_index_optimize_02', @enabled=1, @notify_level_eventlog=0, @notify_level_email=0, @notify_level_netsend=0, @notify_level_page=0, @delete_level=0, @description=N'No description available.', @category_name=N'[Uncategorized (Local)]', @owner_login_name=N'sa', @job_id = @jobId OUTPUT IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback /****** Object: Step [DBA_index_optimize_01] Script Date: 11/9/2020 2:34:44 PM ******/ EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @job_id=@jobId, @step_name=N'DBA_index_optimize_01', @step_id=1, @cmdexec_success_code=0, @on_success_action=1, @on_success_step_id=0, @on_fail_action=2, @on_fail_step_id=0, @retry_attempts=0, @retry_interval=0, @os_run_priority=0, @subsystem=N'TSQL', @command=N'exec dbo.[updba_index_fragment_optimize]', @database_name=N'Monitor', @flags=0 IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback EXEC @ReturnCode = msdb.dbo.sp_update_job @job_id = @jobId, @start_step_id = 1 IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback EXEC @ReturnCode = msdb.dbo.sp_add_jobserver @job_id = @jobId, @server_name = N'(local)' IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback COMMIT TRANSACTION GOTO EndSave QuitWithRollback: IF (@@TRANCOUNT > 0) ROLLBACK TRANSACTION EndSave: GO
job DBA_index_main
USE [msdb] GO /****** Object: Job [DBA_index_main] Script Date: 11/9/2020 3:02:12 PM ******/ BEGIN TRANSACTION DECLARE @ReturnCode INT SELECT @ReturnCode = 0 /****** Object: JobCategory [[Uncategorized (Local)]] Script Date: 11/9/2020 3:02:12 PM ******/ IF NOT EXISTS (SELECT name FROM msdb.dbo.syscategories WHERE name=N'[Uncategorized (Local)]' AND category_class=1) BEGIN EXEC @ReturnCode = msdb.dbo.sp_add_category @class=N'JOB', @type=N'LOCAL', @name=N'[Uncategorized (Local)]' IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback END DECLARE @jobId BINARY(16) EXEC @ReturnCode = msdb.dbo.sp_add_job @job_name=N'DBA_index_main', @enabled=1, @notify_level_eventlog=0, @notify_level_email=0, @notify_level_netsend=0, @notify_level_page=0, @delete_level=0, @description=N'No description available.', @category_name=N'[Uncategorized (Local)]', @owner_login_name=N'sa', @job_id = @jobId OUTPUT IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback /****** Object: Step [updba_index_main] Script Date: 11/9/2020 3:02:13 PM ******/ EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @job_id=@jobId, @step_name=N'updba_index_main', @step_id=1, @cmdexec_success_code=0, @on_success_action=1, @on_success_step_id=0, @on_fail_action=2, @on_fail_step_id=0, @retry_attempts=0, @retry_interval=0, @os_run_priority=0, @subsystem=N'TSQL', @command=N' exec updba_index_main', @database_name=N'Monitor', @flags=0 IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback EXEC @ReturnCode = msdb.dbo.sp_update_job @job_id = @jobId, @start_step_id = 1 IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback EXEC @ReturnCode = msdb.dbo.sp_add_jobserver @job_id = @jobId, @server_name = N'(local)' IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback COMMIT TRANSACTION GOTO EndSave QuitWithRollback: IF (@@TRANCOUNT > 0) ROLLBACK TRANSACTION EndSave: GO
- 以下内容介绍如何根据需要个性化定制:
1. 维护时间窗口设置:
2.只维护特定的db, 特定的table, 获取索引碎片参数,历史记录保存时间
3. 修改procedure [updba_index_fragment] 逻辑过滤不需要维护的索引, 建议修改条件同时维护小表
- update: procedure help to update statistics
USE [Monitor] GO IF EXISTS (SELECT * FROM monitor.sys.objects WHERE object_id = OBJECT_ID(N'dbo.updba_stat_update') AND type in (N'P ')) drop procedure dbo.updba_stat_update go USE [monitor] GO /* drop table monitor.dbo.dba_stat exec [dbo].[updba_stat_update] select * from monitor.dbo.dba_stat */ CREATE proc [dbo].[updba_stat_update] @db_list nvarchar(max) = NULL --NULL all database , run database demo: N'dbname1,dbname2' as set nocount on begin IF NOT EXISTS (SELECT * FROM monitor.sys.objects WHERE object_id = OBJECT_ID(N'monitor.dbo.dba_stat') AND type in (N'U')) begin create table monitor.dbo.dba_stat( id int identity(1,1) primary key ,batch int ,intime datetime default getdate() ,start_update datetime ,end_update datetime ,note varchar(200) ,db varchar(100) ,schema_name varchar(25) ,table_name varchar(200) ,stat_name varchar(300) ,last_updated datetime ,modification_counter int ) create index ix_dba_stat_intime on dba_stat(intime) end BEGIN delete monitor.dbo.dba_stat where intime<dateadd(day,-30,getdate()) --delete history data。 END declare @batch int select top(1) @batch=batch from monitor.dbo.dba_stat with(nolock) order by id desc if @batch is null set @batch=1 else set @batch=@batch+1 if object_id('tempdb.dbo.#dblist','U') is not null drop table #dblist create table #dblist (db varchar(400)) insert into #dblist(db) select T.name from( SELECT name = PARSENAME( LTRIM(RTRIM(T.c.value('.[1]', 'sysname'))), 1 ) FROM( SELECT database_name_list = CONVERT(xml, N'<c><![CDATA[' + REPLACE( REPLACE( REPLACE(@db_list,CHAR(13),CHAR(10) ), CHAR(10),N',' ), N',',N']]></c><c><![CDATA[' ) + N']]></c>' ) )REQ CROSS APPLY REQ.database_name_list.nodes('/c/text()') T(c) ) T inner join sys.databases d on T.name=d.name and d.state=0 WHERE T.name > N'' if not exists (select * from #dblist) begin insert into #dblist(db) SELECT name FROM sys.databases DB WITH(NOLOCK) WHERE name NOT IN('master', 'tempdb', 'model', 'msdb', 'distribution','monitor' ) and state =0 --online end declare @sql_stat nvarchar(max) select @sql_stat='insert into monitor.dbo.dba_stat(batch,db,schema_name,table_name,stat_name,last_updated,modification_counter) SELECT @batch1,@db,SCHEMA_NAME(obj.schema_id) schema_name, obj.name table_name, stat.name stat_name, last_updated, modification_counter FROM sys.objects AS obj INNER JOIN sys.stats AS stat ON stat.object_id = obj.object_id CROSS APPLY sys.dm_db_stats_properties(stat.object_id, stat.stats_id) AS sp WHERE obj.type in (''U'') and modification_counter > 5000; ' DECLARE CUR_DB CURSOR LOCAL READ_ONLY FORWARD_ONLY STATIC FOR SELECT db FROM #dblist DECLARE @sql nvarchar(max), @database_name sysname; OPEN CUR_DB; FETCH CUR_DB INTO @database_name WHILE @@FETCH_STATUS = 0 BEGIN; SET @sql = N'USE ' + QUOTENAME(@database_name)+char(13)+ @sql_stat EXEC sp_executesql @sql,N'@batch1 int,@db nvarchar(100)',@batch1=@batch,@db=@database_name FETCH CUR_DB INTO @database_name END; CLOSE CUR_DB; DEALLOCATE CUR_DB; --run update commands declare @update_sql nvarchar(max),@id int while 1=1 begin select @update_sql=null,@id=null select top(1) @id=id, @update_sql='USE '+QUOTENAME(db)+' UPDATE STATISTICS '+QUOTENAME([schema_name])+'.'+QUOTENAME(table_name)+' '+QUOTENAME(stat_name) from monitor.dbo.dba_stat where batch=@batch and start_update is null if @id is null break update monitor.dbo.dba_stat set start_update=getdate() where id=@id BEGIN TRY EXECUTE ( @update_sql) END TRY BEGIN CATCH update monitor.dbo.dba_stat set end_update=getdate(),note=cast( ERROR_NUMBER() as varchar(50))+ERROR_MESSAGE() where id=@id continue END CATCH; update monitor.dbo.dba_stat set end_update=getdate() where id=@id end end GO