sql >> Database >  >> RDS >> Database

Kerel, van wie is die #temp-tafel?

Je hebt waarschijnlijk in een scenario gezeten waarin je nieuwsgierig was naar wie een specifieke kopie van een #temp-tabel heeft gemaakt. In juni 2007 vroeg ik om een ​​DMV om #temp-tabellen toe te wijzen aan sessies, maar dit werd afgewezen voor de release van 2008 (en werd weggevaagd met de pensionering van Connect een paar jaar geleden).

In SQL Server 2005, 2008 en 2008 R2 zou u deze informatie uit de standaardtracering moeten kunnen halen:

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

(Gebaseerd op code door Jonathan Kehayias.)

Om het ruimtegebruik te bepalen, kunt u dit verder verbeteren door gegevens van DMV's zoals sys.dm_db_partition_stats mee te nemen. – bijvoorbeeld:

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

Vanaf SQL Server 2012 werkte dit echter niet meer als de #temp-tabel een hoop was. Bob Ward (@bobwardms) legde grondig uit waarom dit gebeurde; het korte antwoord is dat er een fout in hun logica zat om te proberen de #temp-tabelcreatie uit de standaardtracering te filteren, en deze bug is gedeeltelijk gecorrigeerd tijdens het SQL Server 2012-werk om tracering en uitgebreide gebeurtenissen beter op elkaar af te stemmen. Houd er rekening mee dat SQL Server 2012+ het maken van #temp-tabellen nog steeds vastlegt met inline-beperkingen zoals een primaire sleutel, maar niet met stapels.

[Klik hier om de volledige uitleg van Bob te tonen/verbergen.]

De gebeurtenis Object:Created heeft eigenlijk 3 subgebeurtenissen:Begin, Commit en Rollback. Dus als je met succes een object maakt, krijg je 2 gebeurtenissen:1 voor Begin en 1 voor Commit. Je weet welke door naar EventSubClass te kijken.


Vóór SQL Server 2012 heeft alleen Object:Created with subclass =Begin de ObjectName ingevuld. Dus de subklasse =Commit bevatte niet de ObjectName die was ingevuld. Dit was zo ontworpen om herhaling van de gedachte te voorkomen dat je de naam zou kunnen opzoeken in het Begin-evenement.


Zoals ik al zei, is de standaardtracering ontworpen om alle traceergebeurtenissen over te slaan waarbij de dbid =2 en de objectnaam met "#" begonnen. Dus wat kan verschijnen in de standaardtracering zijn de Object:Created subklasse =Commit-gebeurtenissen (daarom is de objectnaam leeg).


Hoewel we onze "intenties" om tempdb-objecten niet te traceren niet hebben gedocumenteerd, werkte het gedrag duidelijk niet zoals bedoeld.


Ga nu verder met het bouwen van SQL Server 2012. We gaan over op een proces van het overzetten van gebeurtenissen van SQLTrace naar XEvent. We hebben tijdens dit tijdsbestek, als onderdeel van dit XEvent-werk, besloten dat de subclass=Commit of Rollback de ObjectName nodig had. De code waarin we dit doen, is dezelfde code waarin we de SQLTrace-gebeurtenis produceren, dus nu heeft de SQLTrace-gebeurtenis de ObjectName erin voor de subclass=Commit.


En aangezien onze filterlogica voor standaardtracering niet is gewijzigd, ziet u nu geen Begin- of Commit-gebeurtenissen.

Hoe je het vandaag moet doen

In SQL Server 2012 en hoger kunt u met Extended Events handmatig de object_created . vastleggen evenement, en het is gemakkelijk om een ​​filter toe te voegen om alleen te letten op namen die beginnen met # . De volgende sessiedefinitie legt alle #temp-tabellen vast, heap of niet, en bevat alle nuttige informatie die normaal gesproken zou worden opgehaald uit de standaardtracering. Bovendien legt het de SQL-batch vast die verantwoordelijk is voor het maken van de tabel (als u dat wilt), informatie die niet beschikbaar is in de standaardtracering (TextData is altijd 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;

Je kunt misschien iets soortgelijks doen in 2008 en 2008 R2, maar ik weet dat er enkele subtiele verschillen zijn met wat beschikbaar is, en ik heb het niet getest nadat ik deze fout meteen kreeg:

Msg 25623, Level 16, State 1, Line 1
De gebeurtenisnaam, "sqlserver.object_created", is ongeldig of het object kon niet worden gevonden

De gegevens analyseren

Het ophalen van de informatie uit het bestandsdoel is iets omslachtiger dan met de standaardtracering, vooral omdat het allemaal wordt opgeslagen als XML (nou ja, om pedant te zijn, het is XML gepresenteerd als NVARCHAR). Hier is een query die ik heb gemaakt om informatie te retourneren die vergelijkbaar is met de tweede query hierboven tegen de standaardtracering. Een belangrijk ding om op te merken is dat Extended Events zijn gegevens opslaat in UTC, dus als uw server is ingesteld op een andere tijdzone, moet u deze aanpassen zodat de create_date in sys.objects wordt vergeleken alsof het UTC is. (De tijdstempels zijn ingesteld om overeen te komen omdat object_id waarden kunnen worden hergebruikt. Ik neem hier aan dat een venster van twee seconden voldoende is om eventuele gerecyclede waarden eruit te filteren.)

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

Dit geeft natuurlijk alleen ruimte en andere informatie terug voor #temp-tabellen die nog bestaan. Als je alle #temp-tabelcreaties wilt zien die nog steeds beschikbaar zijn in het bestandsdoel, zelfs als ze nu niet bestaan, verander dan gewoon beide instanties van INNER JOIN naar LEFT OUTER JOIN .


  1. GTT-tabelstatistieken en SYS.WRI$_OPTSTAT_TAB_HISTORY

  2. Node.js en Microsoft SQL Server

  3. Hoe een batch invoegen in MySQL

  4. MySQL-gebruikersbeheer