sql >> Databasteknik >  >> RDS >> Database

Du, vem äger det där #temp-bordet?

Du har förmodligen varit i ett scenario där du var nyfiken på vem som skapade en specifik kopia av en #temp-tabell. Tillbaka i juni 2007 bad jag om en DMV för att mappa #temp-tabeller till sessioner, men detta avvisades för 2008 års utgåva (och sopades bort med Connect-pensioneringen för ett par år sedan).

I SQL Server 2005, 2008 och 2008 R2 bör du kunna hämta denna information från standardspårningen:

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

(Baserat på kod av Jonathan Kehayias.)

För att fastställa utrymmesanvändning kan du förbättra detta ytterligare för att ansluta till data från DMV:er som sys.dm_db_partition_stats – till exempel:

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

Från och med SQL Server 2012 slutade detta dock att fungera om #temp-tabellen var en hög. Bob Ward (@bobwardms) gav en grundlig förklaring till varför detta hände; det korta svaret är att det fanns en bugg i deras logik för att försöka filtrera bort #temp-tabellskapandet från standardspårningen, och denna bugg korrigerades delvis under SQL Server 2012-arbetet med att bättre anpassa spårning och utökade händelser. Observera att SQL Server 2012+ fortfarande kommer att fånga #temp tabellskapande med inline-begränsningar som en primärnyckel, bara inte heaps.

[Klicka här för att visa/dölja Bobs fullständiga förklaring.]

Händelsen Object:Created har faktiskt 3 underhändelser:Begin, Commit och Rollback. Så om du lyckas skapa ett objekt får du 2 händelser:1 för Begin och 1 för Commit. Du vet vilken genom att titta på EventSubClass.


Före SQL Server 2012 var det bara Object:Created with subclass =Begin som hade ObjectName ifyllt. Så underklassen =Commit innehöll inte objektnamnet ifyllt. Detta var designat för att undvika att upprepa detta tänkande att du kunde slå upp namnet i Begin-eventet.


Som jag har sagt var standardspårningen utformad för att hoppa över alla spårningshändelser där dbid =2 och objektnamnet började med "#". Så det som kan dyka upp i standardspåret är underklassen Object:Created =Commit-händelser (vilket är anledningen till att objektnamnet är tomt).


Även om vi inte dokumenterade våra "avsikter" att inte spåra tempdb-objekt, fungerade uppenbarligen inte beteendet som det var tänkt.


Gå nu vidare till bygget av SQL Server 2012. Vi går över till en process för att porta händelser från SQLTrace till XEvent. Vi beslutade under denna tidsram som en del av detta XEvent-arbete att underklassen=Commit eller Rollback behövde fylla i ObjectName. Koden där vi gör detta är samma kod som vi producerar SQLTrace-händelsen så nu har SQLTrace-händelsen ObjectName i sig för subklassen=Commit.


Och eftersom vår filtreringslogik för standardspårning inte har ändrats, ser du nu varken Start- eller Commit-händelser.

Så ska du göra idag

I SQL Server 2012 och uppåt låter utökade händelser dig manuellt fånga object_created händelse, och det är lätt att lägga till ett filter för att bara bry sig om namn som börjar med # . Följande sessionsdefinition kommer att fånga all #temp-tabellskapande, heap eller inte, och kommer att inkludera all användbar information som normalt skulle hämtas från standardspårningen. Dessutom fångar den SQL-batchen som är ansvarig för tabellskapandet (om du vill det), information som inte är tillgänglig i standardspårningen (TextData är alltid 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;

Du kanske kan göra något liknande i 2008 och 2008 R2, men jag vet att det finns några subtila skillnader mot vad som är tillgängligt, och jag testade det inte efter att jag fick det här felet direkt:

Msg 25623, Level 16, State 1, Line 1
Händelsenamnet, "sqlserver.object_created", är ogiltigt, eller så kunde objektet inte hittas

Analysera data

Att dra informationen från filmålet är lite mer besvärligt än med standardspårningen, mest för att allt lagras som XML (nåja, för att vara pedantisk, det är XML som presenteras som NVARCHAR). Här är en fråga som jag skapade för att returnera information som liknar den andra frågan ovan mot standardspåret. En viktig sak att notera är att Extended Events lagrar sina data i UTC, så om din server är inställd på en annan tidszon måste du justera så att create_date i sys.objects jämförs som om det vore UTC. (Tidsstämplarna är inställda på att matcha eftersom object_id värden kan återvinnas. Jag antar här att ett två sekunders fönster är tillräckligt för att filtrera bort eventuella återvunna värden.)

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

Naturligtvis kommer detta bara att returnera utrymme och annan information för #temp-tabeller som fortfarande finns. Om du vill se alla #temp-tabellskapelser som fortfarande är tillgängliga i filmålet, även om de inte existerar nu, ändra helt enkelt båda instanserna av INNER JOIN till LEFT OUTER JOIN .


  1. TIMESTAMPDIFF() Exempel – MySQL

  2. Django och postgresql-scheman

  3. Skillnaden mellan SYSDATE() och NOW() i MariaDB

  4. SQL:Analysera kommaavgränsad sträng och använd som join