嵌套光标上的动态重建索引

Posted

技术标签:

【中文标题】嵌套光标上的动态重建索引【英文标题】:Dynamic Rebuild Index on nested Cursor 【发布时间】:2017-10-08 21:22:06 【问题描述】:

我正在尝试创建一个存储过程来重建所有数据库上的所有索引,碎片 >30%

DECLARE @SQL nvarchar(max)
DECLARE @dbname VARCHAR(50)
DECLARE @dbid VARCHAR(50)
DECLARE @SQL2 nvarchar(max)

--Cursor for database names
DECLARE db_cursor CURSOR READ_ONLY FOR  
SELECT name, database_id    FROM sys.databases
WHERE name NOT IN ('master','model','msdb','tempdb')

--opening the cursor and go to first row
OPEN db_cursor   
FETCH NEXT FROM db_cursor INTO @dbname, @dbid 

WHILE @@FETCH_STATUS = 0   
    BEGIN   
    -- Dynamic query to fill @MyIndexFragmented with data
    SET @SQL= 'USE ' + @dbname  + CHAR(13) + 
     '
     DECLARE @IndexName varchar(150) 
     DECLARE @TableName varchar(150)
     DECLARE @Filas INTEGER
     --Declare Table Variable
     DECLARE @MyIndexFragmented TABLE  
     (Databasename varchar(50),  
     TableName varchar(200),  
     IndexName varchar(200), 
     Avg_Fragmentation Decimal, 
     Page_Count INT ) 

     INSERT INTO @MyIndexFragmented
       SELECT DB_NAME(database_id) AS DatabaseName, 
        OBJECT_NAME(ips.object_id) AS TableName, 
        i.name AS IndexName,
        avg_fragmentation_in_percent, page_count
            FROM sys.dm_db_index_physical_stats (DB_ID(), NULL, NULL , NULL, ''LIMITED'') ips
            INNER JOIN sys.indexes i 
                ON i.object_id = ips.object_id 
                AND i.index_id = ips.index_id
            INNER JOIN sys.partitions p 
                ON p.object_id = i.object_id 
                AND p.index_id = i.index_id
            WHERE avg_fragmentation_in_percent >= 30 
            AND ips.index_id > 0 
            AND page_count > 1000
            ORDER BY avg_fragmentation_in_percent DESC
            set @filas= (select count (*) from @MyIndexFragmented)
            IF @filas>0 
            BEGIN 
            -- nested cursor
                DECLARE index_cursor CURSOR FOR  
                SELECT IndexName, TableName FROM @MyIndexFragmented 

                OPEN index_cursor   
                -- Nos vamos a la primera fila
                FETCH NEXT FROM index_cursor INTO  @Indexname, @TableName
                WHILE @@FETCH_STATUS = 0   
                BEGIN  
                    ALTER INDEX  @IndexName  ON  @TableName  REBUILD
                    --Next Row
                    FETCH NEXT FROM index_cursor INTO  @Indexname, @TableName
                END  
                 CLOSE index_cursor
                 DEALLOCATE index_cursor
            END -- FOR FILAS >0'

      EXECUTE SP_EXECUTESQL @SQL

       FETCH NEXT FROM db_cursor INTO @dbname, @dbid
    end
CLOSE db_cursor   
DEALLOCATE db_cursor

我收到了这个错误:

“@IndexName”附近的语法不正确。

所以我找不到动态制作的方法:

ALTER INDEX @IndexName ON @TableName REBUILD

有什么建议吗?

谢谢大家

【问题讨论】:

调用SP_ExecuteSQL之前需要组装SQL,即set @SQL = 'alter index ' + QuoteName( @IndexName ) + ' on ' + QuoteName( @TableName ) + ' rebuild';。例如,alter index 的文档显示它接受 index_name,而不是可能计算为索引名称的表达式。 感谢您的回复:在这种情况下,我正在组装sql。 不,您使用的是乐观编程。仅仅因为你想让它做某事并不能让它发生。 declare @Expression as VarChar(10) = '3 * 5'; select @Expression; 不会返回 15。同样,您不能alter index @IndexName ...。如果您组装零件,例如'alter index ' + @IndexName + ...,可以创建有效的SQL语句。 你说得对,HABO。我需要重做我的 sql 语句。谢谢 不要重新发明*** - 再一次......只需使用Ola Hallengren's excellent index rebuild script,它已被全球数千名 DBA 证明...... 【参考方案1】:

这是我的最终代码,正在运行。如果对试图重建/重组所有数据库上的所有碎片索引的人有帮助(>5 30 重建) 这是代码

CREATE PROCEDURE [dbo].[IndexManagement] 
-- Parameters
AS
BEGIN
SET NOCOUNT ON

    DECLARE @command nvarchar(max), @Satetement nvarchar(max)
    DECLARE @action varchar(15)
    IF OBJECT_ID('tempdb..#TableList') IS NOT NULL 
    DROP TABLE #TableList  
    CREATE TABLE #TableList 
    (
    ID INT IDENTITY
    ,DbName varchar(100)
    ,TableList nvarchar(100)
    ,IndexName nvarchar(500)
    ,SchemaName nvarchar(500)
    ,fragmentation float
    )


    SET @Satetement ='
     INSERT INTO #TableList(DbName,TableList,IndexName,SchemaName,fragmentation )  
      SELECT 
          DB_NAME(DB_ID()),
          dbtables.[name],
          dbindexes.[name],
          dbschemas.[name],
          CAST(avg_fragmentation_in_percent  AS DECIMAL(18,2))avg_fragmentation_in_percent  
     FROM sys.dm_db_index_physical_stats (DB_ID(), NULL, NULL, NULL, NULL) AS indexstats 
     INNER JOIN sys.tables dbtables on dbtables.[object_id] = indexstats.[object_id] 
     INNER JOIN sys.schemas dbschemas on dbtables.[schema_id] = dbschemas.[schema_id] 
     INNER JOIN sys.indexes AS dbindexes ON dbindexes.[object_id] = indexstats.[object_id] 
     AND indexstats.index_id = dbindexes.index_id WHERE indexstats.database_id = DB_ID() 
     AND indexstats.avg_fragmentation_in_percent BETWEEN 5 AND 100 
     AND indexstats.index_id > 0 
     AND page_count > 1000
     '
     SELECT @command = 'IF ''?'' NOT IN (''msdb'', ''master'',''model'',''tempdb'' ) BEGIN USE ? EXEC ('''+ @Satetement+''') END ' +CHAR(13)+CHAR(10) 


    EXEC sp_MSforeachdb @command


    --Empty command
    SET @command=''

    -- ACLARACION -FILLFACTOR
    -- Deberia ser de acuerdo a los updates de las tablas
    -- EJ: Tablas que el indice es el identity column --> 100%
    --     Tablas estaticas                           --> 100%  
    --     Tablas casi estaticas                      --> 95%
    --     Tablas con mucho movimiento (de un 70 a un 90)
    --      ojo-- en estas ultimas empezar por un 90 y observarlas como se fragmentan
            DECLARE @fillfactor INT = 90
            ,@MinID INT
            ,@MaxID INT
            ,@Table VARCHAR(100)
            ,@Database VARCHAR(100)
            ,@GetIndex VARCHAR(500)
            ,@GetSchema VARCHAR(500)
            ,@fragmentation float
            ,@SQl nvarchar(max)


    SELECT @MinID=MIN(Id) ,@MaxID=MAX(Id) from #TableList  where IndexName IS NOT NULL

        WHILE (@MinID<=@MaxID)
        BEGIN
        SELECT DISTINCT @Table=TableList,
                        @Database=DbName,
                        @GetIndex=IndexName,
                        @GetSchema=SchemaName,
                        @fragmentation = fragmentation

                FROM #TableList where id=@MinID AND IndexName IS NOT NULL

            --SEPARAMOS PARA REBUILD O PARA OEROGANIZE 
            IF @fragmentation > 5 AND @fragmentation <30
                BEGIN
                    SET @command= 'ALTER INDEX '+ @GetIndex +' ON ['+ @Database+'].['+@GetSchema+ +'].['+@Table +'] REORGANIZE'  
                    SET @action='REORGANIZE'
                END
                ELSE
                BEGIN
                    SET @command = ' ALTER INDEX '+@GetIndex+' ON [' + @Database+']'+'.['+@GetSchema+ ']'+'.['+@Table +']'+ 'REBUILD WITH (FILLFACTOR = ' + CONVERT(VARCHAR(3),@fillfactor) + ')' 
                    SET @action='REBUILD'
                END

         --  PRINT @command
         EXEC ( @command)

         --Insert values into IndexMaintenance table
                Insert Into IndexMaintenanceHistory
                (DatabaseName,TableName, IndexName, Fragmentation, Operation, OnDate)
                VALUES 
                (@Database,@Table,@GetIndex, @fragmentation, @action, GETDATE())


          SET @MinID=@MinID+1
        END

    --SELECT * FROM #TableList
    DROP TABLE #TableList


END /* PROCEDURE */

【讨论】:

【参考方案2】:

如果碎片超过 30%,此动态代码将帮助您重新构建索引,此代码可能会帮助您(尽量避免游标在表上应用锁)

DECLARE @command nvarchar(max),@Satetement nvarchar(max)

IF OBJECT_ID('tempdb..##TableList') IS NOT NULL 
DROP TABLE ##TableList  
CREATE TABLE ##TableList 
(
ID INT IDENTITY
,DbName varchar(100)
,TableList nvarchar(100)
,IndexName nvarchar(500)
,SchemaName nvarchar(500)
)
SET @Satetement ='
 INSERT INTO ##TableList(DbName,TableList,IndexName,SchemaName)  
  SELECT 
      DB_NAME(DB_ID()),
      dbtables.[name],
      dbindexes.[name],
      dbschemas.[name]   
 FROM sys.dm_db_index_physical_stats (DB_ID(), NULL, NULL, NULL, NULL) AS indexstats 
 INNER JOIN sys.tables dbtables on dbtables.[object_id] = indexstats.[object_id] 
 INNER JOIN sys.schemas dbschemas on dbtables.[schema_id] = dbschemas.[schema_id] 
 INNER JOIN sys.indexes AS dbindexes ON dbindexes.[object_id] = indexstats.[object_id] 
 AND indexstats.index_id = dbindexes.index_id WHERE indexstats.database_id = DB_ID() 
 AND indexstats.avg_fragmentation_in_percent >=30 
 AND indexstats.index_id > 0 
 AND page_count > 1000
 '
 SELECT @command = 'IF ''?'' IN (''SqlClass'',''AdventureWorks2012'') BEGIN USE ? EXEC ('''+ @Satetement+''') END ' +CHAR(13)+CHAR(10) 

 PRINT @command

EXEC sp_MSforeachdb @command

SET @command=''

DECLARE @fillfactor INT =85
        ,@MinID INT
        ,@MaxID INT
        ,@Table VARCHAR(100)
        ,@Database VARCHAR(100)
        ,@GetIndex VARCHAR(500)
        ,@GetSchema VARCHAR(500)
        ,@SQl nvarchar(max)

SELECT @MinID=MIN(Id) ,@MaxID=MAX(Id) from ##TableList  where IndexName IS NOT NULL

WHILE (@MinID<=@MaxID)
Begin
SELECT DISTINCT @Table=TableList,
                @Database=DbName,
                @GetIndex=IndexName,
                @GetSchema=SchemaName

        FROM ##TableList where id=@MinID AND IndexName IS NOT NULL

  SET @command = ' ALTER INDEX '+@GetIndex+' ON [' + @Database+']'+'.['+@GetSchema+ ']'+'.['+@Table +']'+ ' REBUILD WITH (FILLFACTOR = ' + CONVERT(VARCHAR(3),@fillfactor) + ')' 

  EXEC ( @command)
  PRINT @GetIndex +' index was Rebuild'
  --PRINT (@command) 

  SET @MinID=@MinID+1
END

执行上述脚本后,通过运行以下查询检查更改的索引填充因子值

SELECT 

      DB_NAME() AS Database_Name
    , sc.name AS Schema_Name
    , o.name AS Table_Name
    , o.type_desc
    , i.name AS Index_Name
    , i.type_desc AS Index_Type
    , i.fill_factor
FROM sys.indexes i
INNER JOIN sys.objects o ON i.object_id = o.object_id
INNER JOIN sys.schemas sc ON o.schema_id = sc.schema_id
WHERE i.name IS NOT NULL
AND o.type = 'U'
AND i.fill_factor not in (0, 100)
ORDER BY i.fill_factor DESC, o.name

【讨论】:

【参考方案3】:

非常感谢您的早期回复。 Sreenu131 ,你的建议对我很有效。

为了循环所有数据库(系统数据库除外),我只更改了一行

SELECT @command = 'IF ''?'' NOT IN (''msdb'', ''master'',''model'',''tempdb'' ) BEGIN USE ? EXEC ('''+ @Satetement+''') END ' +CHAR(13)+CHAR(10) 

非常感谢!!!

【讨论】:

您应该通过检查他的答案旁边的复选标记 () 来接受他的回答,而不是通过重复他的回答的要点和“谢谢”来确认 his answer。在此处阅读有关此礼仪的更多信息:What should I do when someone answers my question?。

以上是关于嵌套光标上的动态重建索引的主要内容,如果未能解决你的问题,请参考以下文章

ElasticSearch线上索引重建

oracle索引问题,删除再重建索引与索引分析

在Oracle数据库中按用户名重建索引的方法

重组索引、重建索引和重组表

MSSQL 重建索引(在线重建控制最大处理器数 MAXDOP )

SQL Server查看索引重建重组索引进度