2017-02-09 97 views
0

我试图创建一个服务器范围的触发器,以防止压缩或分区在任何有索引的索引中丢失。现在,它只是在开发中。触发如下:触发器返回意外的结果

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 

当我尝试通过增加一个包含列的索引和取页面压缩掉,以测试它,我得到如下:(旁注:你可能会问我为什么要做到这一点,出于某种未知的原因,这是在生产中使用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

DECLARE @IndexName VARCHAR(100)=(SELECT INDEXNAME FROM #T)很肯定这select返回多行,这是在错误来自。无论如何,它将是该部分的4个声明之一。 –

+0

谢谢!你对此绝对正确。 – PMooney

回答

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中筛选结果。你也可以放弃游标,因为你只需要在一个数据库中查找一个索引。

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