Database
 sql >> Database >  >> RDS >> Database

Amico, chi possiede quel tavolo #temp?

Probabilmente ti sei trovato in uno scenario in cui eri curioso di sapere chi ha creato una copia specifica di una tabella #temp. Nel giugno del 2007, ho chiesto un DMV per mappare le tabelle #temp alle sessioni, ma questo è stato rifiutato per la versione 2008 (ed è stato spazzato via con il ritiro di Connect un paio di anni fa).

In SQL Server 2005, 2008 e 2008 R2, dovresti essere in grado di estrarre queste informazioni dalla traccia predefinita:

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);

(Basato sul codice di Jonathan Kehayias.)

Per determinare l'utilizzo dello spazio è possibile migliorarlo ulteriormente per unire i dati da DMV come sys.dm_db_partition_stats – ad esempio:

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 partire da SQL Server 2012, tuttavia, questo ha smesso di funzionare se la tabella #temp era un heap. Bob Ward (@bobwardms) ha fornito una spiegazione approfondita del motivo per cui ciò è accaduto; la risposta breve è che c'era un bug nella loro logica per cercare di filtrare la creazione della tabella #temp dalla traccia predefinita e questo bug è stato parzialmente corretto durante il lavoro di SQL Server 2012 per allineare meglio la traccia e gli eventi estesi. Tieni presente che SQL Server 2012+ acquisirà comunque la creazione di tabelle #temp con vincoli inline come una chiave primaria, ma non heap.

[Clicca qui per mostrare/nascondere la spiegazione completa di Bob.]

L'evento Object:Created ha in realtà 3 sottoeventi:Begin, Commit e Rollback. Quindi, se crei un oggetto con successo, ottieni 2 eventi:1 per Begin e 1 per Commit. Sai quale guardando EventSubClass.


Prima di SQL Server 2012, solo Object:Created with subclass =Begin aveva ObjectName popolato. Quindi la sottoclasse =Commit non conteneva l'oggetto ObjectName popolato. Questo è stato progettato per evitare di ripetere questo pensiero che potresti cercare il nome nell'evento Begin.


Come ho detto, la traccia predefinita è stata progettata per saltare qualsiasi evento di traccia in cui dbid =2 e il nome dell'oggetto iniziassero con "#". Quindi ciò che può apparire nella traccia predefinita sono la sottoclasse Object:Created =Commit events (motivo per cui il Nome oggetto è vuoto).


Anche se non abbiamo documentato le nostre "intenzioni" di non tracciare oggetti tempdb, il comportamento chiaramente non funzionava come previsto.


Ora passiamo alla creazione di SQL Server 2012. Passiamo a un processo di porting degli eventi da SQLTrace a XEvent. Durante questo lasso di tempo, come parte di questo lavoro XEvent, abbiamo deciso che la sottoclasse=Commit o Rollback richiedeva il popolamento di ObjectName. Il codice in cui lo facciamo è lo stesso codice in cui produciamo l'evento SQLTrace, quindi ora l'evento SQLTrace contiene l'ObjectName per la sottoclasse=Commit.


E poiché la nostra logica di filtraggio per la traccia predefinita non è cambiata, ora non vedi né Begin né Commit eventi.

Come dovresti farlo oggi

In SQL Server 2012 e versioni successive, gli eventi estesi ti consentiranno di acquisire manualmente object_created evento ed è facile aggiungere un filtro che tenga conto solo dei nomi che iniziano con # . La definizione di sessione seguente catturerà tutta la creazione di tabelle #temp, heap o meno, e includerà tutte le informazioni utili che normalmente verrebbero recuperate dalla traccia predefinita. Inoltre, acquisisce il batch SQL responsabile della creazione della tabella (se lo desideri), informazioni non disponibili nella traccia predefinita (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;

Potresti essere in grado di fare qualcosa di simile nel 2008 e nel 2008 R2, ma so che ci sono alcune sottili differenze rispetto a ciò che è disponibile e non l'ho testato dopo aver ricevuto questo errore subito:

Msg 25623, livello 16, stato 1, riga 1
Il nome dell'evento, "sqlserver.object_created", non è valido oppure non è stato possibile trovare l'oggetto

Analisi dei dati

Estrarre le informazioni dal file di destinazione è un po' più macchinoso rispetto alla traccia predefinita, soprattutto perché è tutto archiviato come XML (beh, per essere pedanti, è XML presentato come NVARCHAR). Ecco una query che ho creato per restituire informazioni simili alla seconda query precedente rispetto alla traccia predefinita. Una cosa importante da notare è che gli eventi estesi memorizzano i suoi dati in UTC, quindi se il tuo server è impostato su un altro fuso orario, dovrai regolare in modo che il create_date in sys.objects viene confrontato come se fosse UTC. (I timestamp sono impostati in modo che corrispondano perché object_id i valori possono essere riciclati. Presumo qui che una finestra di due secondi sia sufficiente per filtrare eventuali valori riciclati.)

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];

Ovviamente questo restituirà solo spazio e altre informazioni per le tabelle #temp che esistono ancora. Se vuoi vedere tutte le creazioni di tabelle #temp ancora disponibili nella destinazione del file, anche se non esistono ora, cambia semplicemente entrambe le istanze di INNER JOIN a LEFT OUTER JOIN .