Database
 sql >> Baza danych >  >> RDS >> Database

Koleś, kto jest właścicielem tego stołu #temp?

Prawdopodobnie byłeś w scenariuszu, w którym ciekawiło Cię, kto utworzył konkretną kopię tabeli #temp. W czerwcu 2007 r. poprosiłem o DMV do mapowania tabel #temp do sesji, ale zostało to odrzucone w wersji 2008 (i zostało zmiecione wraz z przejściem na emeryturę Connect kilka lat temu).

W SQL Server 2005, 2008 i 2008 R2 powinieneś być w stanie pobrać te informacje z domyślnego śledzenia:

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

(Na podstawie kodu Jonathana Kehayiasa)

Aby określić wykorzystanie miejsca, możesz dodatkowo ulepszyć to, aby dołączyć dane z DMV, takich jak sys.dm_db_partition_stats – na przykład:

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

Jednak począwszy od SQL Server 2012 to przestało działać, jeśli tabela #temp była stertą. Bob Ward (@bobwardms) dostarczył dokładne wyjaśnienie, dlaczego tak się stało; Krótka odpowiedź brzmi, że w ich logice był błąd, aby spróbować odfiltrować tworzenie tabeli #temp z domyślnego śledzenia, a ten błąd został częściowo naprawiony podczas pracy SQL Server 2012 nad lepszym wyrównywaniem śledzenia i rozszerzonymi zdarzeniami. Zwróć uwagę, że SQL Server 2012+ nadal będzie przechwytywał tworzenie tabeli #temp z ograniczeniami wbudowanymi, takimi jak klucz podstawowy, ale nie stosy.

[Kliknij tutaj, aby pokazać/ukryć pełne wyjaśnienie Boba.]

Zdarzenie Object:Created ma w rzeczywistości 3 zdarzenia podrzędne:Begin, Commit i Rollback. Więc jeśli pomyślnie utworzysz obiekt, otrzymasz 2 zdarzenia:1 za Początek i 1 za Zatwierdzenie. Wiesz który, patrząc na EventSubClass.


Przed SQL Server 2012 tylko Object:Created with subclass =Begin ma wypełnioną ObjectName. Tak więc podklasa =Commit nie zawierała wypełnionej ObjectName. Było to zgodne z projektem, aby uniknąć powtarzania tego myślenia, że ​​możesz wyszukać nazwę w zdarzeniu Początek.


Jak już powiedziałem, domyślny ślad został zaprojektowany tak, aby pomijać wszelkie zdarzenia śledzenia, w których dbid =2 i nazwa obiektu zaczynały się od "#". Tak więc to, co może pojawić się w domyślnym śledzeniu, to zdarzenia Object:Created =Commit (dlatego nazwa obiektu jest pusta).


Mimo że nie udokumentowaliśmy naszych „zamiarów”, aby nie śledzić obiektów tempdb, zachowanie wyraźnie nie działało zgodnie z przeznaczeniem.


Teraz przejdź do budowy SQL Server 2012. Przechodzimy do procesu przenoszenia zdarzeń z SQLTrace do XEvent. Zdecydowaliśmy w tym czasie w ramach tej pracy XEvent, że subclass=Commit lub Rollback wymaga wypełnienia ObjectName. Kod, w którym to robimy, jest tym samym kodem, w którym tworzymy zdarzenie SQLTrace, więc teraz zdarzenie SQLTrace ma w sobie ObjectName dla subclass=Commit.


A ponieważ nasza logika filtrowania dla śledzenia domyślnego nie uległa zmianie, teraz nie widzisz ani zdarzeń Begin, ani Commit.

Jak powinieneś to zrobić dzisiaj

W SQL Server 2012 i nowszych, zdarzenia rozszerzone umożliwiają ręczne przechwytywanie object_created zdarzenie i łatwo jest dodać filtr, aby dbać tylko o nazwy zaczynające się od # . Poniższa definicja sesji przechwyci całe tworzenie tabeli #temp, sterty lub nie, i będzie zawierać wszystkie przydatne informacje, które normalnie zostałyby pobrane z domyślnego śledzenia. Ponadto przechwytuje wsad SQL odpowiedzialny za tworzenie tabeli (jeśli chcesz), informacje niedostępne w domyślnym śledzeniu (TextData jest zawsze 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żesz być w stanie zrobić coś podobnego w 2008 i 2008 R2, ale wiem, że istnieją pewne subtelne różnice w tym, co jest dostępne, i nie przetestowałem tego po otrzymaniu tego błędu od razu:

Msg 25623, poziom 16, stan 1, wiersz 1
Nazwa zdarzenia „sqlserver.object_created” jest nieprawidłowa lub nie można znaleźć obiektu

Analiza danych

Pobieranie informacji z docelowego pliku jest trochę bardziej kłopotliwe niż w przypadku domyślnego śledzenia, głównie dlatego, że wszystkie są przechowywane jako XML (cóż, aby być pedantycznym, jest to XML przedstawiony jako NVARCHAR). Oto zapytanie, które przygotowałem, aby zwrócić informacje podobne do drugiego zapytania powyżej w stosunku do domyślnego śledzenia. Ważną rzeczą do zapamiętania jest to, że zdarzenia rozszerzone przechowują swoje dane w UTC, więc jeśli twój serwer jest ustawiony na inną strefę czasową, będziesz musiał dostosować tak, aby create_date w sys.objects jest porównywany tak, jakby to był czas UTC. (Znaczniki czasowe są ustawione tak, aby pasowały, ponieważ object_id wartości można poddać recyklingowi. Zakładam tutaj, że dwusekundowe okno wystarczy, aby odfiltrować wszelkie wartości z recyklingu.)

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

Oczywiście zwróci to tylko spację i inne informacje dla tabel #temp, które nadal istnieją. Jeśli chcesz zobaczyć wszystkie kreacje tabel #temp nadal dostępne w pliku docelowym, nawet jeśli teraz nie istnieją, po prostu zmień oba wystąpienia INNER JOIN do LEFT OUTER JOIN .


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Jak grupować według roku w SQL

  2. Jak dodać pozycje rankingowe do wierszy za pomocą DENSE_RANK() w SQL?

  3. Łączenie SAS JMP z Salesforce.com

  4. Jak uzyskać wszystkie możliwe kombinacje wierszy z dwóch tabel w SQL?

  5. Co to jest ODBC?