嵌套光标上的动态重建索引
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?。以上是关于嵌套光标上的动态重建索引的主要内容,如果未能解决你的问题,请参考以下文章