sql >> Databáze >  >> RDS >> Database

Kámo, kdo vlastní ten #temp stůl?

Pravděpodobně jste byli ve scénáři, kdy vás zajímalo, kdo vytvořil konkrétní kopii tabulky #temp. V červnu 2007 jsem požádal o DMV pro mapování #temp tabulek na relace, ale to bylo zamítnuto pro verzi 2008 (a bylo smeteno s ukončením Connect před několika lety).

V SQL Server 2005, 2008 a 2008 R2 byste měli být schopni získat tyto informace z výchozího trasování:

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

(Založeno na kódu od Jonathana Kehayiase.)

Chcete-li určit využití prostoru, můžete to dále vylepšit a připojit data z DMV, jako je sys.dm_db_partition_stats – například:

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

Počínaje SQL Serverem 2012 to však přestalo fungovat, pokud byla tabulka #temp hromada. Bob Ward (@bobwardms) poskytl důkladné vysvětlení, proč se to stalo; Krátká odpověď je, že v jejich logice byla chyba při pokusu odfiltrovat vytvoření tabulky #temp z výchozího trasování a tato chyba byla částečně opravena během práce na serveru SQL Server 2012 na lepším zarovnání trasování a rozšířených událostí. Všimněte si, že SQL Server 2012+ bude stále zachycovat vytváření #temp tabulky s vloženými omezeními, jako je primární klíč, ale ne hromady.

[Kliknutím sem zobrazíte/skryjete Bobovo úplné vysvětlení.]

Událost Object:Created má ve skutečnosti 3 dílčí události:Begin, Commit a Rollback. Takže pokud úspěšně vytvoříte objekt, získáte 2 události:1 pro Begin a 1 pro Commit. Poznáte kterou, když se podíváte na EventSubClass.


Před verzí SQL Server 2012 byl název ObjectName vyplněn pouze u Object:Created s podtřídou =Begin. Takže podtřída =Commit neobsahovala vyplněný ObjectName. Toto bylo záměrné, aby se předešlo opakování této myšlenky, že byste mohli vyhledat jméno v události Begin.


Jak jsem řekl, výchozí trasování bylo navrženo tak, aby přeskakovalo všechny události trasování, kde dbid =2 a název objektu začínaly „#“. Ve výchozím trasování se tedy může zobrazit podtřída Object:Created =události Commit (proto je název objektu prázdný).


I když jsme nezdokumentovali naše „záměry“ nesledovat objekty tempdb, chování zjevně nefungovalo tak, jak bylo zamýšleno.


Nyní přejděte k budování SQL Server 2012. Přejdeme k procesu portování událostí z SQLTrace do XEvent. Během tohoto časového rámce jsme se v rámci této práce XEvent rozhodli, že podtřída=Commit nebo Rollback potřebuje vyplnit ObjectName. Kód, ve kterém to děláme, je stejný kód, ve kterém vytváříme událost SQLTrace, takže událost SQLTrace má nyní v sobě ObjectName pro podtřídu=Commit.


A protože se naše logika filtrování pro výchozí trasování nezměnila, nyní nevidíte události Begin ani Commit.

Jak byste to dnes měli udělat

V SQL Server 2012 a novějších vám Extended Events umožní ručně zachytit object_created událost a je snadné přidat filtr, který se bude starat pouze o názvy, které začínají # . Následující definice relace zachytí veškeré vytváření #temp tabulky, hromadu nebo ne, a bude zahrnovat všechny užitečné informace, které by se normálně získaly z výchozího trasování. Kromě toho zachycuje dávku SQL odpovědnou za vytvoření tabulky (pokud to chcete), informace, které nejsou dostupné ve výchozím trasování (TextData je vždy 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;

Možná budete schopni udělat něco podobného v roce 2008 a 2008 R2, ale vím, že existují určité jemné rozdíly oproti tomu, co je k dispozici, a poté, co jsem dostal tuto chybu, jsem to hned netestoval:

Msg 25623, Level 16, State 1, Line 1
Název události „sqlserver.object_created“ je neplatný nebo objekt nebyl nalezen

Analýza dat

Vytahování informací z cíle souboru je o něco těžkopádnější než u výchozího trasování, většinou proto, že je vše uloženo jako XML (no, abych byl pedantský, je to XML prezentované jako NVARCHAR). Zde je dotaz, který jsem vytvořil, abych vrátil informace podobné druhému dotazu výše proti výchozímu trasování. Jedna důležitá věc, kterou je třeba poznamenat, je, že Extended Events ukládá svá data v UTC, takže pokud je váš server nastaven na jiné časové pásmo, budete muset upravit tak, aby create_date v sys.objects je porovnáváno, jako by to bylo UTC. (Časová razítka jsou nastavena tak, aby se shodovala, protože object_id hodnoty lze recyklovat. Předpokládám, že k odfiltrování recyklovaných hodnot stačí dvousekundové okno.)

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

Samozřejmě to vrátí pouze prostor a další informace pro #temp tabulky, které stále existují. Pokud chcete vidět všechny #temp vytvořené tabulky, které jsou stále dostupné v cíli souboru, i když nyní neexistují, jednoduše změňte obě instance INNER JOIN na LEFT OUTER JOIN .


  1. Znamená omezení Postgres UNIQUE index?

  2. Jak používat datový typ Postgres JSONB s JPA?

  3. Jak funguje DB_NAME() na serveru SQL Server

  4. Výběr všech záznamů pomocí dotazu SQL LIMIT a OFFSET