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

Alter, wem gehört dieser #temp-Tisch?

Wahrscheinlich waren Sie schon einmal in einem Szenario, in dem Sie neugierig waren, wer eine bestimmte Kopie einer #temp-Tabelle erstellt hat. Im Juni 2007 bat ich um eine DMV, um #temp-Tabellen Sessions zuzuordnen, aber dies wurde für die Version 2008 abgelehnt (und vor ein paar Jahren mit der Einstellung von Connect weggefegt).

In SQL Server 2005, 2008 und 2008 R2 sollten Sie diese Informationen aus der Standardablaufverfolgung abrufen können:

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

(Basierend auf Code von Jonathan Kehayias.)

Um die Speicherplatznutzung zu bestimmen, könnten Sie dies weiter verbessern, um Daten von DMVs wie sys.dm_db_partition_stats einzufügen – zum Beispiel:

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

Ab SQL Server 2012 funktionierte dies jedoch nicht mehr, wenn die #temp-Tabelle ein Heap war. Bob Ward (@bobwardms) lieferte eine gründliche Erklärung, warum dies geschah; Die kurze Antwort ist, dass es einen Fehler in ihrer Logik gab, um zu versuchen, die Erstellung von #temp-Tabellen aus der Standardablaufverfolgung herauszufiltern, und dieser Fehler wurde teilweise während der SQL Server 2012-Arbeit zur besseren Ausrichtung von Ablaufverfolgungs- und erweiterten Ereignissen behoben. Beachten Sie, dass SQL Server 2012+ weiterhin die Erstellung von #temp-Tabellen mit Inline-Einschränkungen wie einem Primärschlüssel erfasst, nur nicht mit Heaps.

[Klicken Sie hier, um Bobs vollständige Erklärung anzuzeigen/auszublenden.]

Das Object:Created-Ereignis hat eigentlich 3 Unterereignisse:Begin, Commit und Rollback. Wenn Sie also erfolgreich ein Objekt erstellen, erhalten Sie 2 Ereignisse:1 für Begin und 1 für Commit. Sie wissen welche, indem Sie sich EventSubClass ansehen.


Vor SQL Server 2012 wurde der ObjectName nur für Object:Created with subclass =Begin ausgefüllt. Die Unterklasse =Commit enthielt also keinen gefüllten Objektnamen. Dies war beabsichtigt, um zu vermeiden, dass Sie diesen Gedanken wiederholen, Sie könnten den Namen im Begin-Ereignis nachschlagen.


Wie ich bereits sagte, wurde der Standard-Trace entwickelt, um alle Trace-Ereignisse zu überspringen, bei denen dbid =2 und der Objektname mit "#" begannen. Was also im Standard-Trace auftauchen kann, sind die Ereignisse Object:Created subclass =Commit (weshalb der Objektname leer ist).


Obwohl wir unsere "Absichten", tempdb-Objekte nicht zu verfolgen, nicht dokumentiert haben, funktionierte das Verhalten eindeutig nicht wie beabsichtigt.


Fahren Sie nun mit dem Erstellen von SQL Server 2012 fort. Wir bewegen uns zu einem Prozess zum Portieren von Ereignissen von SQLTrace zu XEvent. Wir haben während dieses Zeitrahmens als Teil dieser XEvent-Arbeit entschieden, dass die subclass=Commit oder Rollback den Objektnamen ausgefüllt benötigen. Der Code, in dem wir dies tun, ist derselbe Code, in dem wir das SQLTrace-Ereignis erzeugen, also enthält das SQLTrace-Ereignis jetzt den ObjectName für subclass=Commit.


Und da sich unsere Filterlogik für den Standard-Trace nicht geändert hat, sehen Sie jetzt weder Begin- noch Commit-Ereignisse.

Wie Sie es heute tun sollten

In SQL Server 2012 und höher ermöglichen erweiterte Ereignisse die manuelle Erfassung des object_created Ereignis, und es ist einfach, einen Filter hinzuzufügen, um nur Namen zu berücksichtigen, die mit # beginnen . Die folgende Sitzungsdefinition erfasst die gesamte Erstellung von #temp-Tabellen, Heap oder nicht, und enthält alle nützlichen Informationen, die normalerweise aus der Standardablaufverfolgung abgerufen würden. Darüber hinaus erfasst es den SQL-Batch, der für die Tabellenerstellung verantwortlich ist (falls gewünscht), Informationen, die in der Standardablaufverfolgung nicht verfügbar sind (TextData ist immer 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;

Möglicherweise können Sie in 2008 und 2008 R2 etwas Ähnliches tun, aber ich weiß, dass es einige subtile Unterschiede zu dem gibt, was verfügbar ist, und ich habe es nicht getestet, nachdem dieser Fehler sofort aufgetreten ist:

Msg 25623, Level 16, State 1, Line 1
Der Ereignisname „sqlserver.object_created“ ist ungültig oder das Objekt konnte nicht gefunden werden

Analyse der Daten

Das Abrufen der Informationen aus dem Dateiziel ist etwas umständlicher als mit dem Standard-Trace, vor allem, weil alles als XML gespeichert wird (na ja, um es pedantisch zu sagen, es ist XML, das als NVARCHAR dargestellt wird). Hier ist eine Abfrage, die ich erstellt habe, um Informationen ähnlich der zweiten obigen Abfrage für die Standardablaufverfolgung zurückzugeben. Beachten Sie unbedingt, dass Extended Events seine Daten in UTC speichert. Wenn Ihr Server also auf eine andere Zeitzone eingestellt ist, müssen Sie das create_date anpassen in sys.objects wird verglichen, als ob es UTC wäre. (Die Zeitstempel sind so eingestellt, dass sie übereinstimmen, weil object_id Werte können recycelt werden. Ich gehe hier davon aus, dass ein Zwei-Sekunden-Fenster ausreicht, um alle recycelten Werte herauszufiltern.)

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

Dies gibt natürlich nur Speicherplatz und andere Informationen für #temp-Tabellen zurück, die noch vorhanden sind. Wenn Sie alle noch im Dateiziel verfügbaren #temp-Tabellenerstellung sehen möchten, auch wenn sie jetzt nicht existieren, ändern Sie einfach beide Instanzen von INNER JOIN zu LEFT OUTER JOIN .