我正在尝试创建一个服务器范围的触发器,以防止压缩或分区在任何拥有它的索引上丢失。现在,它只在开发中。触发器如下:
CREATE TRIGGER [dbtrg_PREVENT_COMPRESSED_PARTITION_LOSS_ON_INDEXES] ON ALL SERVER
AFTER DDL_EVENTS
AS
SET NOCOUNT ON;
CREATE TABLE #t
(
ServerName sysname,
DatabaseName sysname,
TableName sysname,
IndexName sysname,
PartitionNumber INT,
CompressionType NVARCHAR(60)
);
DECLARE @dbName sysname;
DECLARE @dbCursor CURSOR;
DECLARE @sql NVARCHAR(MAX);
SET
@dbCursor = CURSOR
FOR SELECT name
FROM sys.databases
WHERE source_database_id IS NULL
AND database_id > 4
AND NAME <> 'AdventureWorks2008R2'
AND is_read_only = 0
AND state_desc = 'ONLINE'
ORDER BY name;
OPEN @dbCursor;
FETCH NEXT FROM @dbCursor INTO @dbName;
WHILE ( @@FETCH_STATUS = 0 )
BEGIN
SET @sql = 'USE [' + @dbName + ' ]
INSERT INTO #t
SELECT @@SERVERNAME AS ServerName,
DB_NAME() AS DatabaseName,
st.name AS TableName,
si.name AS IndexName,
sp.partition_number AS PartitionNumber,
sp.data_compression_desc AS CompressionType
FROM sys.partitions SP WITH (NOLOCK)
LEFT JOIN sys.tables ST WITH (NOLOCK) ON st.object_id = sp.object_id
LEFT OUTER JOIN sys.indexes SI WITH (NOLOCK) ON sp.object_id = si.object_id
AND sp.index_id = si.index_id
AND st.object_id = si.object_id
WHERE st.type = ''U''
AND data_compression <> 0
ORDER BY st.name, si.index_id, si.name, sp.partition_number';
EXECUTE sp_executesql @sql;
FETCH NEXT FROM @dbCursor INTO @dbName;
END;
CLOSE @dbCursor;
DEALLOCATE @dbCursor;
DECLARE @xmlEventData XML = EVENTDATA();;
DECLARE @IndexName VARCHAR(100) = (SELECT IndexName FROM #t)
DECLARE @QueryBody VARCHAR(MAX) = (SELECT CONVERT(VARCHAR(MAX), @xmlEventData.query('data(/EVENT_INSTANCE/TSQLCommand/CommandText)')))
DECLARE @eventType SYSNAME = @xmlEventData.value('(/EVENT_INSTANCE/EventType)[1]','nvarchar(128)');
IF (@eventType IN ('ALTER INDEX', 'CREATE INDEX', 'DROP INDEX'))
BEGIN
IF (@QueryBody LIKE '%' + @IndexName + '%') AND NOT ((@QueryBody LIKE '%REORG%') OR (@QueryBody LIKE '%REBUILD%')) OR NOT (@QueryBody LIKE '%PAGE_COMPRESSION%')
ROLLBACK;
RAISERROR ('This index is either partitioned, compressed, or both. Please see the DBAs to update this index', 16, 1) WITH LOG;
RETURN;
END;
ENABLE TRIGGER [dbtrg_PREVENT_COMPRESSED_PARTITION_LOSS_ON_INDEXES] ON ALL SERVER
GO
当我尝试通过向索引添加INCLUDE列并关闭页面压缩来测试它时,我得到以下结果:(旁注:你可能会问我为什么这样做。出于某种未知的原因,这已经发生在生产中使用GUI,我们希望确保它在将来不会发生。)
/*------------------------
USE [AdventureWorks2012]
GO
CREATE UNIQUE NONCLUSTERED INDEX [AK_SalesOrderDetail_rowguid] ON [Sales].[SalesOrderDetail]
(
[rowguid] ASC
)
INCLUDE ( [SalesOrderID]) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = ON, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
------------------------*/
Msg 512, Level 16, State 1, Procedure dbtrg_PREVENT_COMPRESSED_PARTITION_LOSS_ON_INDEXES, Line 78
Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.
The statement has been terminated.
当然,这不是我指定的错误。任何人都可以告诉我我在哪里出错吗?
答案 0 :(得分:0)
Gareth说得对。 DECLARE @IndexName VARCHAR(100) = (SELECT IndexName FROM #t)
返回所有数据库中的所有索引名称。我想你想使用以下方法收集受影响的实际索引名称:
架构:@xmlEventData.value('(/EVENT_INSTANCE/SchemaName)[1]', 'sysname')
名称:@xmlEventData.value('(/EVENT_INSTANCE/ObjectName)[1]', 'sysname')
然后在#t
中查找索引信息。
您还应该能够检索事件的数据库。我实际上会考虑将索引名称查找移动到触发器的开头,然后在#t
中过滤结果。您可能还可以删除光标,因为您只需要在一个数据库中查找一个索引。
答案 1 :(得分:0)
Gareth对子查询是正确的。但是,光标在触发器中的临时表将不起作用。它与SQL Server在引用临时表本身之前已经删除索引有关。重新安排逻辑是徒劳的。最终,答案是创建一个由代理作业提供的持久表。该表保留了光标和临时表最初提供的信息。触发器然后查看表格的状况,这很有效。谢谢大家,谢谢你们(以及我工作的同事,帮助解决这个非常模糊的问题非常宝贵)。这是有效的代码。我希望它能帮助处于类似位置的其他人:
USE [master]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TRIGGER [dbtrg_PREVENT_COMPRESSED_PARTITION_LOSS_ON_INDEXES] ON ALL SERVER
AFTER CREATE_INDEX, DROP_INDEX, ALTER_INDEX
AS
SET NOCOUNT ON;
DECLARE @xmlEventData XML = EVENTDATA();;
DECLARE @CurrentIndexName NVARCHAR(100) = ( SELECT
CONVERT(NVARCHAR(MAX), @xmlEventData.query('data(/EVENT_INSTANCE/ObjectName)')) );
DECLARE @QueryBody NVARCHAR(MAX) = ( SELECT
CONVERT(NVARCHAR(MAX), @xmlEventData.query('data(/EVENT_INSTANCE/TSQLCommand/CommandText)')) );
DECLARE @eventType SYSNAME = @xmlEventData.value('(/EVENT_INSTANCE/EventType)[1]', 'varchar(128)');
PRINT @eventType
PRINT @QueryBody
PRINT @CurrentIndexName
IF EXISTS ( SELECT
IndexName
FROM
dbo.CompressedIndexes
WHERE
IndexName = @CurrentIndexName )
AND ( @QueryBody LIKE '%' + @CurrentIndexName + '%' )
AND NOT ( ( @QueryBody LIKE '%REORG%' )
OR ( @QueryBody LIKE '%REBUILD%' )
)
OR NOT ( @QueryBody LIKE '%PAGE_COMPRESSION%' )
BEGIN
RAISERROR ('This index is either partitioned, compressed, or both. Please see the DBAs to update this index', 16, 1) WITH LOG;
ROLLBACK;
END;
RETURN;
ENABLE TRIGGER [dbtrg_PREVENT_COMPRESSED_PARTITION_LOSS_ON_INDEXES] ON ALL SERVER;
GO