SQL Server 索引管理——索引备份

作为一个DBA,做任何操作(尤其是删除、更新等)之前,首先要想到事情是,操作如何还原,对于索引的操作也是如此。前文分别叙述了无用索引删除脚本的生成、以及禁用脚本的生成。对于禁用的索引,只需要将禁用脚本中的disable改为REBUILD即可重建恢复。而禁用脚本,在最终确认无用时,还是要删除掉的,所以对于这类要删除的索引,在删除之前,我们还是要备份其创建脚本的。下面我直接给出生成创建索引脚本的两个过程:

/*
	获取索引的键列、包含列、分区列信息
*/
CREATE PROCEDURE dba_getIndexColomnStr
	@objectId INT
	,@indexId INT
	,@isPartion BIT		--0表示索引未分区,1表示索引分区
	,@keyColumnStr VARCHAR(100) OUTPUT
	,@includeColumnStr VARCHAR(100) OUTPUT
	,@partitonColumnName VARCHAR(50) OUTPUT
AS
	
BEGIN
SET NOCOUNT ON;
SELECT c.[object_id],index_id,name column_name,key_ordinal,partition_ordinal,is_descending_key,is_included_column
INTO #temp
FROM sys.index_columns ic
LEFT JOIN sys.columns c ON ic.[object_id]=c.[object_id] AND ic.column_id=c.column_id
WHERE ic.[object_id]=@objectId AND index_id=@indexId;

 SELECT @keyColumnStr=STUFF((
                     SELECT column_name+CASE WHEN is_descending_key=0 THEN ' ASC ' ELSE ' DESC' END +N','
                     FROM #temp
                     WHERE is_included_column=0 AND key_ordinal>0
                     ORDER BY key_ordinal
                     FOR XML PATH(''),TYPE).value('.','varchar(max)'),1,0,'');
SELECT @includeColumnStr=STUFF((
                     SELECT column_name + N','
                     FROM #temp
                     WHERE is_included_column=1
                     ORDER BY key_ordinal
                     FOR XML PATH(''),TYPE).value('.','varchar(max)'),1,0,'');
SELECT @keyColumnStr=LEFT(@keyColumnStr,LEN(@keyColumnStr)-1),@includeColumnStr=LEFT(@includeColumnStr,LEN(@includeColumnStr)-1);
	IF @isPartion=1 
		SELECT 
			@partitonColumnName=column_name
		FROM #temp
		WHERE partition_ordinal>0;
	ELSE
		SET @partitonColumnName='';

	TRUNCATE TABLE #temp;
	DROP TABLE #temp;

END;
GO

此过程传入对象编号和索引编号,返回索引键列、包含列字符串,如果索引有分区,还会返回分区列。下面是生成索引创建脚本的过程:

/*
	生成索引的创建脚本
	测试
	exec dba_generateCreateCodeOfDisabledIndexes 0
*/
ALTER PROCEDURE dba_generateCreateCodeOfDisabledIndexes
	@isDisableIndex BIT=1	--0生成数据库下所有表的所有索引的创建脚本,1生成数据库中禁用索引的创建脚本,默认值为1
AS
BEGIN
DECLARE @createIndexCode VARCHAR(MAX)
	,@keyColumnStr VARCHAR(100)		--索引键列组(包括升降序)
	,@includeColumnStr VARCHAR(100)	--索引包含列组
	,@objectId INT
	,@indexId INT
	,@indexName VARCHAR(100)
	,@indexTypeDesc VARCHAR(50)	--索引类型
	,@isUnique VARCHAR(10)		--是否唯一索引
	,@ignoreDupKey BIT	--是否忽略重复键
	,@isPadded BIT	--是否使用空格对齐
	,@isPrimaryKey BIT	--是否为主键
	,@allowRowLock BIT	--是否允许行锁
	,@allowPageLock BIT	--是否允许页锁
	,@filterDefinition VARCHAR(50)	--过滤定义
	,@fillFactor INT	--填充因子
	,@partitionSchemesName VARCHAR(50)	--分区方案名
	,@partitionColumnName VARCHAR(50)	--分区列
	,@indexOptions VARCHAR(200)			--索引配置选项
	,@fileGroupName VARCHAR(50)			--索引所在的文件组
	,@schemaName VARCHAR(50)
	,@tableName VARCHAR(50)
	,@indexType INT
	,@compressionDelay INT
	,@sql VARCHAR(MAX);

--索引压缩、索引分区、内存表索引,聚集索引、非聚集索引、唯一索引,主键索引

--无分区的索引创建脚本生成
DECLARE cur CURSOR FOR(
SELECT t.[object_id],ix.index_id,ix.TYPE indexType,QUOTENAME(SCHEMA_NAME(t.SCHEMA_ID),'[') [schema_name], QUOTENAME(t.name,'['), QUOTENAME(ix.name,'['),
	 CASE WHEN ix.is_unique = 1 THEN 'UNIQUE ' ELSE '' END is_unique
	 ,ix.is_primary_key
	 ,ix.type_desc,
	 CASE WHEN ix.is_padded=1 THEN 'PAD_INDEX = ON, ' ELSE 'PAD_INDEX = OFF, ' END
	 + CASE WHEN ix.ALLOW_PAGE_LOCKS=1 THEN 'ALLOW_PAGE_LOCKS = ON, ' ELSE 'ALLOW_PAGE_LOCKS = OFF, ' END
	 + CASE WHEN ix.ALLOW_ROW_LOCKS=1 THEN  'ALLOW_ROW_LOCKS = ON, ' ELSE 'ALLOW_ROW_LOCKS = OFF, ' END
	 + CASE WHEN INDEXPROPERTY(t.OBJECT_ID, ix.name, 'IsStatistics') = 1 THEN 'STATISTICS_NORECOMPUTE = ON, ' ELSE 'STATISTICS_NORECOMPUTE = OFF, ' END
	 + CASE WHEN ix.IGNORE_DUP_KEY=1 THEN 'IGNORE_DUP_KEY = ON, ' ELSE 'IGNORE_DUP_KEY = OFF' END
	 +CASE p.data_compression_desc WHEN 'page' THEN ', DATA_COMPRESSION = PAGE' 
			WHEN 'row' THEN ', DATA_COMPRESSION = ROW'
			ELSE '' END
	 + CASE WHEN is_primary_key=1 THEN '' ELSE ', SORT_IN_TEMPDB = OFF' END	--创建主键索引不能加此参数
	 +CASE WHEN ix.fill_factor>0 THEN ', FILLFACTOR =' + CAST(ix.fill_factor AS VARCHAR(6)) ELSE '' END AS IndexOptions
	  ,QUOTENAME(FILEGROUP_NAME(ix.data_space_id),'[') FileGroupName--,p.data_compression_desc
 FROM sys.tables t 
 INNER JOIN sys.indexes ix ON t.OBJECT_ID=ix.OBJECT_ID 
 INNER JOIN sys.partitions p  ON p.[object_id]=ix.[object_id] AND p.index_id=ix.index_id
 WHERE ix.data_space_id>0	--=0是内存表的索引
	AND ix.data_space_id<65536	-->65536 为分区索引
	AND ix.TYPE IN(1,2,5,6)	--1聚集索引,2非聚集索引,5聚集列索引,6非聚集列索引
	AND (CASE WHEN @isDisableIndex=1 THEN ix.is_disabled ELSE 1 END)=1
 --order by schema_name(t.schema_id), t.name, ix.name
 );
 OPEN cur;
 FETCH NEXT FROM cur INTO @objectId,@indexId,@indexType,@schemaName,@tableName,@indexName,@isUnique,@isPrimaryKey,@indexTypeDesc,@indexOptions,@fileGroupName;
 DECLARE @desc VARCHAR(50);
 SET @desc='没有不分区的'+(CASE WHEN @isDisableIndex=1 THEN '禁用' ELSE ''  END) +'索引';
  IF @@fetch_status < 0
    BEGIN
		CLOSE cur;
        DEALLOCATE cur;
        RAISERROR(15472,-1,-1,@desc); -- 没有该类型的索引.
        --return (0)
		GOTO  partitionIndex;
    END;

 WHILE @@FETCH_STATUS=0
 BEGIN
	EXECUTE dba_getIndexColomnStr @objectId,@indexId,0,@keyColumnStr OUTPUT,@includeColumnStr OUTPUT,@partitionColumnName OUTPUT;
	IF @indexType IN(1,2)	--行索引
	BEGIN
		IF @isPrimaryKey=1	--主键索引
			SET @sql='ALTER TABLE ' +@schemaName+'.'+@tableName +' ADD CONSTRAINT '+@indexName+' PRIMARY KEY '+@indexTypeDesc
				+'('+@keyColumnStr+') '
				+'WITH('+@indexOptions+')'
				+' ON ' +@fileGroupName;
		ELSE	--非主键索引
			SET @sql='CREATE '+@isUnique +' '+ @indexTypeDesc +' INDEX '+@indexName+' ON '+@schemaName+'.'+@tableName
					+' (' +@keyColumnStr+') '
					+CASE WHEN @includeColumnStr IS NULL THEN ' ' ELSE ' INCLUDE( '+@includeColumnStr+') ' END
					+'WITH('+@indexOptions+') '
					+'ON ' +@fileGroupName;
	END;
	ELSE IF @indexType IN(5,6)	--列索引
	BEGIN
		SET @sql='CREATE '+@indexTypeDesc+' INDEX '+@indexName+ ' ON ' +@schemaName+'.'+@tableName 
			+'('+@includeColumnStr+') '
			+' ON ' +@fileGroupName;
	END;
	PRINT @sql;
	FETCH NEXT FROM cur INTO @objectId,@indexId,@indexType,@schemaName,@tableName,@indexName,@isUnique,@isPrimaryKey,@indexTypeDesc,@indexOptions,@fileGroupName;
 END;
 CLOSE cur;
 DEALLOCATE cur;

 --分区索引
 partitionIndex:;
DECLARE cur1 CURSOR FOR(
SELECT t.[object_id],ix.index_id,ix.TYPE indexType,QUOTENAME(SCHEMA_NAME(t.SCHEMA_ID),'[') [schemaName]
	, QUOTENAME(t.name,'[') tableName, QUOTENAME(ix.name,'[') indexName
	, CASE WHEN ix.is_unique = 1 THEN 'UNIQUE ' ELSE '' END is_unique
	 ,ix.is_primary_key
	 ,ix.type_desc,
	 CASE WHEN ix.is_padded=1 THEN 'PAD_INDEX = ON, ' ELSE 'PAD_INDEX = OFF, ' END
	 + CASE WHEN ix.ALLOW_PAGE_LOCKS=1 THEN 'ALLOW_PAGE_LOCKS = ON, ' ELSE 'ALLOW_PAGE_LOCKS = OFF, ' END
	 + CASE WHEN ix.ALLOW_ROW_LOCKS=1 THEN  'ALLOW_ROW_LOCKS = ON, ' ELSE 'ALLOW_ROW_LOCKS = OFF, ' END
	 + CASE WHEN INDEXPROPERTY(t.OBJECT_ID, ix.name, 'IsStatistics') = 1 THEN 'STATISTICS_NORECOMPUTE = ON, ' ELSE 'STATISTICS_NORECOMPUTE = OFF, ' END
	 + CASE WHEN ix.IGNORE_DUP_KEY=1 THEN 'IGNORE_DUP_KEY = ON, ' ELSE 'IGNORE_DUP_KEY = OFF ' END
	 + CASE WHEN is_primary_key=1 THEN '' ELSE ', SORT_IN_TEMPDB = OFF' END	--创建主键索引不能加此参数
	 +CASE WHEN ix.fill_factor>0 THEN ', FILLFACTOR =' + CAST(ix.fill_factor AS VARCHAR(6)) ELSE '' END AS IndexOptions
	  ,ps.name partitonSchemesName--,fg.groupname FileGroupName
	  ,ix.compression_delay
 FROM sys.tables t 
 INNER JOIN sys.indexes ix ON t.OBJECT_ID=ix.OBJECT_ID
 INNER JOIN sys.partition_schemes ps ON ps.data_space_id=ix.data_space_id
 WHERE ix.TYPE IN(1,2,5,6)	--1聚集索引,2非聚集索引
	AND ix.data_space_id>65536	--分区索引
	AND (CASE WHEN @isDisableIndex=1 THEN ix.is_disabled ELSE 1 END)=1
);
OPEN cur1;
FETCH NEXT FROM cur1 INTO @objectId,@indexId,@indexType,@schemaName,@tableName,@indexName,@isUnique,@isPrimaryKey
	,@indexTypeDesc,@indexOptions,@partitionSchemesName,@compressionDelay;
SET @desc='不存在分区的'+(CASE WHEN @isDisableIndex=1 THEN '禁用' ELSE '' END) +'索引'; 
  IF @@fetch_status < 0
    BEGIN
		CLOSE cur1;
        DEALLOCATE cur1;
        RAISERROR(15472,-1,-1,@desc); -- 没有该类型的索引.
        --return (0)
		GOTO memoryOptimizedTableIndex;
    END;

	WHILE @@FETCH_STATUS=0
 BEGIN
	EXECUTE dba_getIndexColomnStr @objectId,@indexId,1,@keyColumnStr OUTPUT,@includeColumnStr OUTPUT,@partitionColumnName OUTPUT;
	IF @indexType IN(1,2)
	BEGIN
		IF @isPrimaryKey=1	--主键索引
			SET @sql='ALTER TABLE ' +@schemaName+'.'+@tableName +' ADD CONSTRAINT '+@indexName+' PRIMARY KEY '+@indexTypeDesc
				+'('+@keyColumnStr+') '
				+'WITH('+@indexOptions+')'
				+' ON ' +@fileGroupName;
		ELSE	--非主键索引
			SET @sql='CREATE '+@isUnique +' '+ @indexTypeDesc +' INDEX '+@indexName+' ON '+@schemaName+'.'+@tableName
					+' (' +@keyColumnStr+') '
					+CASE WHEN @includeColumnStr IS NULL THEN ' ' ELSE ' INCLUDE( '+@includeColumnStr+') ' END
					+'WITH('+@indexOptions+') '
					+'ON ' +@partitionSchemesName+'('+@partitionColumnName+')';
		PRINT @sql;
		--生成分区的压缩脚本
		SELECT @sql=STUFF((
						SELECT 
							'ALTER INDEX '+@indexName+' ON '+@schemaName+'.'+@tableName+' REBUILD PARTITION = '+CONVERT(VARCHAR(5),partition_number)+
								' WITH (SORT_IN_TEMPDB = OFF, ONLINE = OFF, DATA_COMPRESSION = '+data_compression_desc+ ' ) '+CHAR(10)
						FROM sys.partitions
						WHERE [object_id]=@objectId AND index_id=@indexId AND data_compression>0
						FOR XML PATH(''),TYPE).value('.','varchar(max)'),1,0,'');
		PRINT @sql;
	END;
	ELSE IF @indexType IN(5,6)	--列索引
	BEGIN
		SET @sql='CREATE '+@indexTypeDesc+' INDEX '+@indexName+ ' ON ' +@schemaName+'.'+@tableName 
			+'('+@includeColumnStr+') '
			+' ON ' +@partitionSchemesName+'('+@partitionColumnName+')';
		PRINT @sql;
		--列索引被禁用后,其在sys.partitions 中的信息消失
		--生成列索引的压缩脚本
		--SELECT @sql=STUFF((
		--				SELECT 
		--					'ALTER INDEX '+@indexName+' ON '+@schemaName+'.'+@tableName+' REBUILD PARTITION = '+CONVERT(VARCHAR(5),partition_number)+
		--						' WITH (DATA_COMPRESSION = '+data_compression_desc+ ' ) '+CHAR(10)
		--				FROM sys.partitions
		--				WHERE [object_id]=@objectId AND index_id=@indexId AND data_compression>0
		--				FOR XML PATH(''),TYPE).value('.','varchar(max)'),1,0,'');
		--PRINT @sql;
	END;
	FETCH NEXT FROM cur1 INTO @objectId,@indexId,@indexType,@schemaName,@tableName,@indexName,@isUnique,@isPrimaryKey
	,@indexTypeDesc,@indexOptions,@partitionSchemesName,@compressionDelay;
 END;
 CLOSE cur1;
 DEALLOCATE cur1;

 columnIndex:;


 memoryOptimizedTableIndex:;

  --内存表索引创建脚本生成
  --内存表索引不能禁用,这里给出的是备份索引的脚本
  DECLARE cur2 CURSOR FOR(
SELECT t.[object_id],ix.index_id
	,QUOTENAME(SCHEMA_NAME(t.SCHEMA_ID),'[') [schemaName]
	, QUOTENAME(t.name,'[') tableName
	, QUOTENAME(ix.name,'[') indexName
	,ix.type_desc
 FROM sys.tables t 
 INNER JOIN sys.indexes ix ON t.OBJECT_ID=ix.OBJECT_ID 
 INNER JOIN sys.partitions p  ON p.[object_id]=ix.[object_id] AND p.index_id=ix.index_id
 WHERE ix.data_space_id=0	--=0是内存表的索引
	AND ix.is_primary_key=0
	AND (CASE WHEN @isDisableIndex=1 THEN ix.is_disabled ELSE 1 END)=1
 --order by schema_name(t.schema_id), t.name, ix.name
 );
 OPEN cur2;
 FETCH NEXT FROM cur2 INTO @objectId,@indexId,@schemaName,@tableName,@indexName,@indexTypeDesc;
 --DECLARE @desc VARCHAR(50);
 SET @desc='没有不分区的'+(CASE WHEN @isDisableIndex=1 THEN '禁用' ELSE ''  END) +'索引';
  IF @@fetch_status < 0
    BEGIN
		CLOSE cur2;
        DEALLOCATE cur2;
        RAISERROR(15472,-1,-1,@desc); -- 没有该类型的索引.
        --return (0)
		GOTO  pEnd;
    END;

	 WHILE @@FETCH_STATUS=0
	 BEGIN
		EXECUTE dba_getIndexColomnStr @objectId,@indexId,0,@keyColumnStr OUTPUT,@includeColumnStr OUTPUT,@partitionColumnName OUTPUT;
	
				SET @sql='ALTER TABLE ' +@schemaName+'.'+@tableName +' ADD INDEX '+@indexName+' '+@indexTypeDesc
					+'('+@keyColumnStr+') ';	

		PRINT @sql;
		 FETCH NEXT FROM cur2 INTO @objectId,@indexId,@schemaName,@tableName,@indexName,@indexTypeDesc;
	 END;
	 CLOSE cur2;
	 DEALLOCATE cur2;
	 pEnd:;
 END;

注意该过程可以生成行索引、列索引以及内存优化表索引创建脚本的生成,对于xml索引、空间索引、全文索引暂未包括,请注意使用。