Você provavelmente já esteve em um cenário em que estava curioso sobre quem criou uma cópia específica de uma tabela #temp. Em junho de 2007, pedi um DMV para mapear tabelas #temp para sessões, mas isso foi rejeitado para o lançamento de 2008 (e foi eliminado com a aposentadoria do Connect alguns anos atrás).
No SQL Server 2005, 2008 e 2008 R2, você deve conseguir extrair essas informações do rastreamento padrão:
DECLARE @filename VARCHAR(MAX); SELECT @filename = SUBSTRING([path], 0, LEN([path])-CHARINDEX('\', REVERSE([path]))+1) + '\Log.trc' FROM sys.traces WHERE is_default = 1; SELECT o.name, o.[object_id], o.create_date, gt.SPID, NTUserName = gt.NTDomainName + '\' + gt.NTUserName, SQLLogin = gt.LoginName, gt.HostName, gt.ApplicationName, gt.TextData -- don't bother, always NULL FROM sys.fn_trace_gettable(@filename, DEFAULT) AS gt INNER JOIN tempdb.sys.objects AS o ON gt.ObjectID = o.[object_id] WHERE gt.DatabaseID = 2 AND gt.EventClass = 46 -- (Object:Created Event from sys.trace_events) AND gt.EventSubClass = 1 -- Commit AND o.name LIKE N'#%' AND o.create_date >= DATEADD(MILLISECOND, -100, gt.StartTime) AND o.create_date <= DATEADD(MILLISECOND, 100, gt.StartTime);
(Baseado no código de Jonathan Kehayias.)
Para determinar o uso do espaço, você pode aprimorar ainda mais isso para juntar dados de DMVs como
sys.dm_db_partition_stats
- por exemplo:DECLARE @filename VARCHAR(MAX); SELECT @filename = SUBSTRING([path], 0, LEN([path])-CHARINDEX('\', REVERSE([path]))+1) + '\Log.trc' FROM sys.traces WHERE is_default = 1; SELECT o.name, o.[object_id], o.create_date, gt.SPID, NTUserName = gt.NTDomainName + '\' + gt.NTUserName, SQLLogin = gt.LoginName, gt.HostName, gt.ApplicationName, row_count = x.rc, reserved_page_count = x.rpc FROM sys.fn_trace_gettable(@filename, DEFAULT) AS gt INNER JOIN tempdb.sys.objects AS o ON gt.ObjectID = o.[object_id] INNER JOIN ( SELECT [object_id], rc = SUM(CASE WHEN index_id IN (0,1) THEN row_count END), rpc = SUM(reserved_page_count) FROM tempdb.sys.dm_db_partition_stats GROUP BY [object_id] ) AS x ON x.[object_id] = o.[object_id] WHERE gt.DatabaseID = 2 AND gt.EventClass = 46 -- (Object:Created Event from sys.trace_events) AND gt.EventSubClass = 1 -- Commit AND gt.IndexID IN (0,1) AND o.name LIKE N'#%' AND o.create_date >= DATEADD(MILLISECOND, -100, gt.StartTime) AND o.create_date <= DATEADD(MILLISECOND, 100, gt.StartTime);
A partir do SQL Server 2012, no entanto, isso parou de funcionar se a tabela #temp fosse um heap. Bob Ward (@bobwardms) forneceu uma explicação completa de por que isso aconteceu; a resposta curta é que houve um bug em sua lógica para tentar filtrar a criação da tabela #temp do rastreamento padrão, e esse bug foi parcialmente corrigido durante o trabalho do SQL Server 2012 de melhor alinhamento de rastreamento e eventos estendidos. Observe que o SQL Server 2012+ ainda capturará a criação de tabela #temp com restrições embutidas, como uma chave primária, mas não heaps.
[Clique aqui para mostrar/ocultar a explicação completa de Bob.]
O evento Object:Created na verdade tem 3 subeventos:Begin, Commit e Rollback. Portanto, se você criar um objeto com sucesso, obterá 2 eventos:1 para Begin e 1 para Commit. Você sabe qual olhando para EventSubClass.
Antes do SQL Server 2012, apenas o Object:Created com subclass =Begin tem o ObjectName preenchido. Portanto, a subclasse =Commit não continha o ObjectName preenchido. Isso ocorreu por design para evitar repetir esse pensamento, você pode procurar o nome no evento Begin.
Como eu disse, o rastreamento padrão foi projetado para pular quaisquer eventos de rastreamento onde o dbid =2 e o nome do objeto começassem com "#". Então, o que pode aparecer no rastreamento padrão são os eventos Object:Created subclass =Commit (e é por isso que o Object Name está em branco).
Apesar de não documentarmos nossas "intenções" de não rastrear objetos tempdb, o comportamento claramente não estava funcionando como pretendido.
Agora vamos para a construção do SQL Server 2012. Passamos para um processo de portar eventos de SQLTrace para XEvent. Decidimos durante esse período de tempo como parte deste trabalho XEvent que a subclass=Commit ou Rollback precisava do ObjectName preenchido. O código onde fazemos isso é o mesmo código onde produzimos o evento SQLTrace, então agora o evento SQLTrace tem o ObjectName nele para a subclass=Commit.
E como nossa lógica de filtragem para rastreamento padrão não mudou, agora você não vê os eventos Begin ou Commit.
Como você deve fazer isso hoje
No SQL Server 2012 e superior, os Eventos Estendidos permitirão que você capture manualmente o
object_created
evento, e é fácil adicionar um filtro para se preocupar apenas com nomes que começam com #
. A definição de sessão a seguir capturará toda a criação de #temp tabela, heap ou não, e incluirá todas as informações úteis que normalmente seriam recuperadas do rastreamento padrão. Além disso, captura o lote SQL responsável pela criação da tabela (se desejar), informações não disponíveis no rastreamento padrão (TextData
é sempre NULL
). CREATE EVENT SESSION [TempTableCreation] ON SERVER ADD EVENT sqlserver.object_created ( ACTION ( -- you may not need all of these columns sqlserver.session_nt_username, sqlserver.server_principal_name, sqlserver.session_id, sqlserver.client_app_name, sqlserver.client_hostname, sqlserver.sql_text ) WHERE ( sqlserver.like_i_sql_unicode_string([object_name], N'#%') AND ddl_phase = 1 -- just capture COMMIT, not BEGIN ) ) ADD TARGET package0.asynchronous_file_target ( SET FILENAME = 'c:\temp\TempTableCreation.xel', -- you may want to set different limits depending on -- temp table creation rate and available disk space MAX_FILE_SIZE = 32768, MAX_ROLLOVER_FILES = 10 ) WITH ( -- if temp table creation rate is high, consider -- ALLOW_SINGLE/MULTIPLE_EVENT_LOSS instead EVENT_RETENTION_MODE = NO_EVENT_LOSS ); GO ALTER EVENT SESSION [TempTableCreation] ON SERVER STATE = START;
Você pode fazer algo semelhante em 2008 e 2008 R2, mas sei que existem algumas diferenças sutis no que está disponível e não testei depois de obter esse erro logo de cara:
Msg 25623, Level 16, State 1, Line 1
O nome do evento, "sqlserver.object_created", é inválido ou o objeto não foi encontrado
Analisando os dados
Puxar as informações do destino do arquivo é um pouco mais complicado do que com o rastreamento padrão, principalmente porque tudo é armazenado como XML (bem, para ser pedante, é XML apresentado como NVARCHAR). Aqui está uma consulta que preparei para retornar informações semelhantes à segunda consulta acima em relação ao rastreamento padrão. Uma coisa importante a ser observada é que o Extended Events armazena seus dados em UTC, portanto, se seu servidor estiver definido para outro fuso horário, você precisará ajustar para que o
create_date
em sys.objects
é comparado como se fosse UTC. (Os carimbos de data/hora são definidos para corresponder porque object_id
valores podem ser reciclados. Suponho aqui que uma janela de dois segundos é suficiente para filtrar quaisquer valores reciclados.) DECLARE @delta INT = DATEDIFF(MINUTE, SYSUTCDATETIME(), SYSDATETIME()); ;WITH xe AS ( SELECT [obj_name] = xe.d.value(N'(event/data[@name="object_name"]/value)[1]',N'sysname'), [object_id] = xe.d.value(N'(event/data[@name="object_id"]/value)[1]',N'int'), [timestamp] = DATEADD(MINUTE, @delta, xe.d.value(N'(event/@timestamp)[1]',N'datetime2')), SPID = xe.d.value(N'(event/action[@name="session_id"]/value)[1]',N'int'), NTUserName = xe.d.value(N'(event/action[@name="session_nt_username"]/value)[1]',N'sysname'), SQLLogin = xe.d.value(N'(event/action[@name="server_principal_name"]/value)[1]',N'sysname'), HostName = xe.d.value(N'(event/action[@name="client_hostname"]/value)[1]',N'sysname'), AppName = xe.d.value(N'(event/action[@name="client_app_name"]/value)[1]',N'nvarchar(max)'), SQLBatch = xe.d.value(N'(event/action[@name="sql_text"]/value)[1]',N'nvarchar(max)') FROM sys.fn_xe_file_target_read_file(N'C:\temp\TempTableCreation*.xel',NULL,NULL,NULL) AS ft CROSS APPLY (SELECT CONVERT(XML, ft.event_data)) AS xe(d) ) SELECT DefinedName = xe.obj_name, GeneratedName = o.name, o.[object_id], xe.[timestamp], o.create_date, xe.SPID, xe.NTUserName, xe.SQLLogin, xe.HostName, ApplicationName = xe.AppName, TextData = xe.SQLBatch, row_count = x.rc, reserved_page_count = x.rpc FROM xe INNER JOIN tempdb.sys.objects AS o ON o.[object_id] = xe.[object_id] AND o.create_date >= DATEADD(SECOND, -2, xe.[timestamp]) AND o.create_date <= DATEADD(SECOND, 2, xe.[timestamp]) INNER JOIN ( SELECT [object_id], rc = SUM(CASE WHEN index_id IN (0,1) THEN row_count END), rpc = SUM(reserved_page_count) FROM tempdb.sys.dm_db_partition_stats GROUP BY [object_id] ) AS x ON o.[object_id] = x.[object_id];
É claro que isso retornará apenas espaço e outras informações para tabelas #temp que ainda existem. Se você quiser ver todas as criações de tabelas #temp ainda disponíveis no destino do arquivo, mesmo que elas não existam agora, basta alterar as duas instâncias de
INNER JOIN
para LEFT OUTER JOIN
.