Meu problema: a execução sp_refreshsqlmodule
atualizará o sys.objects.modify_date
valor, mas não acionará um evento DDL que possa ser usado por um gatilho DDL. Portanto, se estiver usando um gatilho DDL para mostrar o histórico de alterações do objeto, não consigo reconciliar o motivo pelo qual um procedimento, função etc. teria um recente modify_date
quando nenhuma ALTER
instrução foi executada no objeto. Isso pode causar algumas dores de cabeça durante o tempo de auditoria.
Minha pergunta: Existe alguma maneira de capturar sp_refreshsqlmodule
eventos, para que eu possa ter uma maneira automatizada de reconciliar os modify_date
objetos do banco de dados?
Consegui encontrar este item de conexão para SQL Server 2008 https://connect.microsoft.com/SQLServer/feedback/details/340441/sp-autostats-and-other-system-procedures-does-not-fire-ddl- triggers , que é marcado como fechado com o comentário:
não achamos que seja crítico corrigir isso para o SQL Server 2008. Definitivamente, consideraremos isso para o lançamento depois disso
Também sou capaz de replicar o problema no SQL Server 2014.
Script SQL para replicar o problema:
IF OBJECT_ID('dbo.test_DDL_log ') IS NOT NULL
DROP TABLE dbo.test_DDL_log;
GO
CREATE TABLE dbo.test_DDL_log (id int not null identity primary key, DDL_EventData xml, dateCreated datetime, contextInfo varchar(128));
go
CREATE TRIGGER [TEST_ddlDatabaseTriggerLog]
ON DATABASE
FOR DDL_DATABASE_LEVEL_EVENTS
AS
--Log all DDL operations on this database to an audit table.
BEGIN
SET NOCOUNT ON;
INSERT INTO dbo.test_DDL_log(DDL_EventData, dateCreated, contextInfo)
VALUES (
EVENTDATA()
, GETDATE()
, REPLACE(CAST(CONTEXT_INFO() AS VARCHAR(128)), CHAR(0), '')
);
END
GO
--Context info used in DDL trigger to tie to build and/or Support Ticket # to this change
DECLARE @c varbinary(128);
SET @c=cast('Ticket 123 v1.2.345' as varbinary(128));
SET CONTEXT_INFO @c;
GO
--Repro Example taken from: https://msdn.microsoft.com/en-us/library/bb326754.aspx
-- Create an alias type.
IF EXISTS (SELECT 'TEST_mytype' FROM sys.types WHERE name = 'TEST_mytype')
DROP TYPE TEST_mytype;
GO
CREATE TYPE TEST_mytype FROM nvarchar(5);
GO
IF OBJECT_ID ('dbo.TEST_to_upper', 'FN') IS NOT NULL
DROP FUNCTION dbo.TEST_to_upper;
GO
CREATE FUNCTION dbo.TEST_to_upper (@a TEST_mytype)
RETURNS TEST_mytype
WITH ENCRYPTION
AS
BEGIN
RETURN upper(@a)
END;
GO
-- Increase the length of the alias type.
EXEC sp_rename 'TEST_mytype', 'TEST_myoldtype', 'userdatatype';
GO
CREATE TYPE TEST_mytype FROM nvarchar(10);
GO
---- The function parameter still uses the old type.
-- and would Fail here because of truncation:
--SELECT dbo.TEST_to_upper('abcdefgh');
GO
select modify_date_BEFORE = o.modify_date
from sys.objects o
where o.name = 'TEST_to_upper'
;
go
WAITFOR DELAY '00:00:05'; --allow some time to elapse so that modify_date change is more noticable
GO
-- Refresh the function to bind to the renamed type.
EXEC sys.sp_refreshsqlmodule 'dbo.TEST_to_upper';
go
select modify_date_AFTER = o.modify_date
from sys.objects o
where o.name = 'TEST_to_upper'
;
go
--only 4 events show here, NOT included the call to sp_refreshsqlmodule which updated the modify_date
SELECT * from dbo.test_DDL_log;
go
--CLEANUP
IF OBJECT_ID ('dbo.TEST_to_upper', 'FN') IS NOT NULL
DROP FUNCTION dbo.TEST_to_upper;
GO
IF EXISTS (SELECT 'TEST_myoldtype' FROM sys.types WHERE name = 'TEST_myoldtype')
DROP TYPE TEST_myoldtype;
GO
IF EXISTS (SELECT 'TEST_mytype' FROM sys.types WHERE name = 'TEST_mytype')
DROP TYPE TEST_mytype;
GO
if exists(select 1 from sys.triggers t where t.name = 'TEST_ddlDatabaseTriggerLog')
DROP TRIGGER [TEST_ddlDatabaseTriggerLog] ON DATABASE
GO
IF OBJECT_ID('dbo.test_DDL_log ') IS NOT NULL
DROP TABLE dbo.test_DDL_log;
GO
Os resultados deste script mostram:
modify_date_BEFORE
-----------------------
2015-10-16 10:59:10.447
modify_date_AFTER
-----------------------
2015-10-16 10:59:15.487
id DDL_EventData dateCreated contextInfo
----------- ------------------------------------------------------------------------------------------------ ------------------------- ------------------------
1 <EVENT_INSTANCE><EventType>CREATE_TYPE</EventType><PostTime>2015-10-16T10:59:10.443</PostTime>....... 2015-10-16 10:59:10.443 Ticket 123 v1.2.345
2 <EVENT_INSTANCE><EventType>CREATE_FUNCTION</EventType><PostTime>2015-10-16T10:59:10.447</PostTime>... 2015-10-16 10:59:10.447 Ticket 123 v1.2.345
3 <EVENT_INSTANCE><EventType>RENAME</EventType><PostTime>2015-10-16T10:59:10.450</PostTime>............ 2015-10-16 10:59:10.450 Ticket 123 v1.2.345
4 <EVENT_INSTANCE><EventType>CREATE_TYPE</EventType><PostTime>2015-10-16T10:59:10.453</PostTime>....... 2015-10-16 10:59:10.453 Ticket 123 v1.2.345
O evento para o sp_refreshsqlmodule
não foi encontrado em nenhum lugar e todos os eventos registrados são anteriores ao sp_refreshsqlmodule
horário de 16/10/2015 10:59:15.487 (nota: coloquei um atraso de 5 segundos antes da sp_refreshsqlmodule
chamada no script acima para torná-lo mais perceptível que este evento não estava sendo registrado).
Infelizmente, existem vários eventos do tipo DDL que na verdade não contam como eventos DDL para uso com gatilhos DDL, notificações de eventos, etc.
Você pode capturar
sp_refreshsqlmodule
chamadas usando a seguinte sessão de eventos estendidos:Você pode precisar coletar um conjunto diferente de colunas de auditoria; estes foram principalmente emprestados de uma sessão semelhante que eu tenho. Você também pode adicionar um filtro adicional para
sp_refreshview
e possivelmente outros eventos, como recompilações e atualizações de estatísticas (não conheço todas as chamadas de procedimento que podem mudarmodify_date
, mas não são capturadas como eventos DDL adequados).Agora, a sessão apenas capturará os dados. Você pode inspecioná-lo manualmente assim:
Saída de exemplo:
Você terá que analisar o nome do objeto e colocar as informações em seu próprio formato XML para corresponder
EVENTDATA()
. Observe também quetimestamp
está em UTC, não em sua hora local, então você precisará ajustar isso para comparações válidasmodify_date
(que herdam a hora do servidor). No meu caso, a diferença entremodify_date
etimestamp
foi de 6 milissegundos, portanto, a sessão não registra o momento exato em que o objeto foi modificado - você precisará permitir uma pequena margem de manobra para "combinar" esses dois valores.Em seguida, você precisará colocar qualquer código com o qual terminar em algum tipo de trabalho que pesquisa o arquivo de destino para novas linhas (você pode considerar as notificações de consulta para evitar a pesquisa, mas a pesquisa é muito mais simples) e as insere em seu Tabela de auditoria DDL.
A resposta de @AaronBertrand acima funciona bem para SQL 2014 (e provavelmente SQL 2012, embora eu não tenha uma instância de 2012 para testar).
No entanto, se estiver usando o SQL Server 2008 R2, você deve remover as seguintes linhas ao criar a sessão do evento, pois elas causarão os seguintes erros:
Como
SET collect_statement
não funciona no SQL 2008 R2, você deve adicionarsqlserver.sql_text
àAction
lista e o código para analisar o nome do objeto que foi passadosp_refreshsqlmodule
precisa ser capaz de lidar com várias instruções executadas em um lote.Também no SQL2008R2, ao filtrar os valores OBJECT_ID na
where
cláusula doCREATE EVENT SESSION
, você deve usar #'s positivosobject_id
para esses objetos do sistema, mesmo que osobject_id
#'s sejam realmente negativos. Acho que isso ocorre porque no SQL 2008 R2 os eventos estendidos são usadosuint32
internamente para oobject_id
.Aqui está o script para SQL 2008 R2: