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

Dude, hvem ejer det #temp bord?

Du har sikkert været i et scenarie, hvor du var nysgerrig efter, hvem der har lavet en specifik kopi af en #temp-tabel. Tilbage i juni 2007 bad jeg om en DMV til at kortlægge #temp-tabeller til sessioner, men dette blev afvist til 2008-udgivelsen (og blev fejet væk med Connect-pensioneringen for et par år siden).

I SQL Server 2005, 2008 og 2008 R2 bør du kunne hente disse oplysninger fra standardsporingen:

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

(Baseret på kode af Jonathan Kehayias.)

For at bestemme pladsforbruget kan du forbedre dette yderligere for at deltage i data fra DMV'er såsom sys.dm_db_partition_stats – for eksempel:

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

Fra og med SQL Server 2012 stoppede dette dog med at fungere, hvis #temp-tabellen var en bunke. Bob Ward (@bobwardms) gav en grundig forklaring på, hvorfor dette skete; det korte svar er, at der var en fejl i deres logik for at forsøge at filtrere #temp-tabeloprettelse fra standardsporingen, og denne fejl blev delvist rettet under SQL Server 2012-arbejdet med bedre at tilpasse sporing og udvidede hændelser. Bemærk, at SQL Server 2012+ stadig vil fange #temp-tabeloprettelse med inline-begrænsninger såsom en primærnøgle, bare ikke dynger.

[Klik her for at vise/skjule Bobs fulde forklaring.]

Object:Created-begivenheden har faktisk 3 underbegivenheder:Begin, Commit og Rollback. Så hvis du opretter et objekt med succes, får du 2 begivenheder:1 for Begin og 1 for Commit. Du ved hvilken ved at se på EventSubClass.


Før SQL Server 2012 var det kun Object:Created with subclass =Begin, der havde udfyldt ObjectName. Så underklassen =Commit indeholdt ikke det udfyldte objektnavn. Dette var designet for at undgå at gentage denne tankegang, at du kunne slå navnet op i Begin-begivenheden.


Som jeg har sagt var standardsporingen designet til at springe alle sporingshændelser over, hvor dbid =2 og objektnavnet startede med "#". Så det, der kan dukke op i standardsporingen, er underklassen Object:Created =Commit (hvilket er grunden til, at objektnavnet er tomt).


Selvom vi ikke dokumenterede vores "hensigter" om ikke at spore tempdb-objekter, virkede adfærden tydeligvis ikke efter hensigten.


Gå nu videre til opbygningen af ​​SQL Server 2012. Vi går over til en proces med portering af hændelser fra SQLTrace til XEvent. Vi besluttede i løbet af denne tidsramme som en del af dette XEvent-arbejde, at underklassen=Commit eller Rollback skulle udfylde ObjectName. Koden, hvor vi gør dette, er den samme kode, hvor vi producerer SQLTrace-hændelsen, så nu har SQLTrace-hændelsen ObjectName i sig for underklassen=Commit.


Og da vores filtreringslogik for standardsporing ikke har ændret sig, kan du nu hverken se Begynd- eller Commit-begivenheder.

Sådan skal du gøre det i dag

I SQL Server 2012 og nyere vil Extended Events give dig mulighed for manuelt at fange object_created begivenhed, og det er nemt at tilføje et filter for kun at bekymre sig om navne, der starter med # . Den følgende sessionsdefinition vil fange al #temp-tabeloprettelse, heap eller ej, og vil inkludere al den nyttige information, der normalt ville blive hentet fra standardsporingen. Derudover fanger den SQL-batchen, der er ansvarlig for tabeloprettelse (hvis du ønsker det), information ikke tilgængelig i standardsporingen (TextData er altid 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 kan muligvis gøre noget lignende i 2008 og 2008 R2, men jeg ved, at der er nogle subtile forskelle i forhold til, hvad der er tilgængeligt, og jeg testede det ikke efter at have fået denne fejl med det samme:

Msg 25623, Level 16, State 1, Line 1
Hændelsesnavnet, "sqlserver.object_created", er ugyldigt, eller objektet kunne ikke findes

Analyse af dataene

At trække informationen fra filmålet er lidt mere besværligt end med standardsporingen, mest fordi det hele er gemt som XML (nå, for at være pedantisk, så er det XML præsenteret som NVARCHAR). Her er en forespørgsel, jeg har lavet for at returnere oplysninger svarende til den anden forespørgsel ovenfor mod standardsporingen. En vigtig ting at bemærke er, at Extended Events gemmer sine data i UTC, så hvis din server er indstillet til en anden tidszone, bliver du nødt til at justere, så create_date i sys.objects sammenlignes, som om det var UTC. (Tidsstemplerne er indstillet til at matche, fordi object_id værdier kan genbruges. Jeg antager her, at et to sekunders vindue er tilstrækkeligt til at bortfiltrere eventuelle genbrugsværdier.)

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

Dette vil selvfølgelig kun returnere plads og anden information for #temp-tabeller, der stadig eksisterer. Hvis du vil se alle #temp-tabeloprettelser, der stadig er tilgængelige i filmålet, selvom de ikke eksisterer nu, skal du blot ændre begge forekomster af INNER JOIN til LEFT OUTER JOIN .


  1. Anatomi af en softwareudviklingsrolle:Dataforsker

  2. Kapacitetsplanlægning for MySQL og MariaDB - Dimensionering af lagerstørrelse

  3. Sådan håndteres datoer korrekt i forespørgselsbegrænsninger

  4. Hvordan giver man en API-klient 1.000.000 databaseresultater?