Database
 sql >> Base de Dados >  >> RDS >> Database

Cara, quem é o dono dessa mesa #temp?


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 .