Jakie informacje o zdarzeniu mogę domyślnie uzyskać z SQL Server?

60

Często widzę pytania, w których ludzie chcą wiedzieć, czy coś się wydarzyło, kiedy to się wydarzyło lub kto wykonał to działanie. W wielu przypadkach SQL Server po prostu nie śledzi tych informacji. Na przykład:

  • Kto ostatnio wykonał procedurę przechowywaną dbo.MyProcedure?
  • Kto zaktualizował salarykolumnę w dbo.Employeestabeli?
  • Kto ostatnio pytał o dbo.Ordersstół z Management Studio?

Ale istnieje kilka innych imprez, że SQL Server nie zapisuje tymczasowo domyślnie i mogą odpowiedzieć na pytania dotyczące natywnie, takie jak:

  • Kiedy ostatni raz nastąpił automatyczny wzrost w bazie danych AdventureWorks i jak długo to trwało?
  • Kto dbo.EmployeeAuditDatai kiedy usunął tabelę?
  • Ile błędów związanych z pamięcią się dzisiaj wydarzyło?

Jak uzyskać te informacje i jak długo będą one dostępne?

Aaron Bertrand
źródło

Odpowiedzi:

65

Domyślnie SQL Server śledzi dla ciebie sporo cennych informacji. Od SQL Server 2005 istnieje „domyślny ślad”, który działa w tle, a od SQL Server 2008 automatycznie uruchamiana jest sesja Rozszerzonych zdarzeń, zwana system_health.

Można również znaleźć pewne informacje z dziennika SQL Server błędów, dziennika SQL Server agenta, dzienników zdarzeń Windows oraz dodatkowego logowania z rzeczy, takich jak SQL Server Audit , Zarządzania Hurtowni Danych , powiadomień o zdarzeniach , DML wyzwalaczy , DDL wyzwalaczy , SCOM / System Center , własne dane śledzenia po stronie serwera lub sesje zdarzeń rozszerzonych lub rozwiązania monitorujące innych firm (takie jak te wykonane przez mojego pracodawcę, SQL Sentry ). Możesz także opcjonalnie włączyć tak zwane „śledzenie Blackbox”, aby pomóc w rozwiązywaniu problemów .

Ale w tym poście skupię się na rzeczach, które są zazwyczaj włączone wszędzie: domyślnym śledzeniu, sesjach zdarzeń rozszerzonych i dzienniku błędów.

Domyślny ślad

Domyślne śledzenie zwykle działa w większości systemów, chyba że zostało wyłączone przy użyciusp_configure . Tak długo, jak jest włączony, może być bogatym źródłem cennych informacji. Poniżej wymieniono przechwycone zdarzenia śledzenia:

DECLARE @TraceID INT;

SELECT @TraceID = id FROM sys.traces WHERE is_default = 1;

SELECT t.EventID, e.name as Event_Description
  FROM sys.fn_trace_geteventinfo(@TraceID) t
  JOIN sys.trace_events e ON t.eventID = e.trace_event_id
  GROUP BY t.EventID, e.name;

Możesz uzyskać więcej szczegółów, łącząc się, sys.trace_columnsaby zobaczyć, które zdarzenia przychodzą z którymi danymi, ale na razie pominę to, ponieważ możesz zobaczyć, co masz, gdy faktycznie pytasz dane śledzenia dla określonych zdarzeń. Są to zdarzenia, które są dostępne w moim systemie (powinieneś uruchomić zapytanie, aby się upewnić, że się zgadzają, choć nadal jest to ten sam zestaw zdarzeń za pośrednictwem SQL Server 2019 CTP 2.4):

EventID  Event_Description
-------  ----------------------------------------------
18       Audit Server Starts And Stops
20       Audit Login Failed
22       ErrorLog
46       Object:Created
47       Object:Deleted
55       Hash Warning
69       Sort Warnings
79       Missing Column Statistics
80       Missing Join Predicate
81       Server Memory Change
92       Data File Auto Grow
93       Log File Auto Grow
94       Data File Auto Shrink
95       Log File Auto Shrink
102      Audit Database Scope GDR Event
103      Audit Schema Object GDR Event
104      Audit Addlogin Event
105      Audit Login GDR Event
106      Audit Login Change Property Event
108      Audit Add Login to Server Role Event
109      Audit Add DB User Event
110      Audit Add Member to DB Role Event
111      Audit Add Role Event
115      Audit Backup/Restore Event
116      Audit DBCC Event
117      Audit Change Audit Event
152      Audit Change Database Owner
153      Audit Schema Object Take Ownership Event
155      FT:Crawl Started
156      FT:Crawl Stopped
164      Object:Altered
167      Database Mirroring State Change
175      Audit Server Alter Trace Event
218      Plan Guide Unsuccessful

Zauważ, że domyślny ślad korzysta z plików najazdu, więc dane, które masz do dyspozycji, cofną się tylko do tej pory - zakres dat dostępnych danych zależy od liczby przechwytywanych powyżej zdarzeń i częstotliwości. Jeśli chcesz mieć dłuższą historię, możesz skonfigurować zadanie, które okresowo archiwizuje aktualnie nieaktywne pliki powiązane ze śledzeniem.

Przykłady

W pytaniu zadałem kilka pytań, które znalazłem. Oto przykładowe zapytania dotyczące pobierania określonych informacji z domyślnego śledzenia.

Pytanie: Kiedy ostatni raz miał miejsce automatyczny wzrost w bazie danych AdventureWorks i jak długo to trwało?

To zapytanie spowoduje wyciągnięcie wszystkich zdarzeń AutoGrow z bazy danych AdventureWorks, zarówno dla plików dziennika, jak i danych, które nadal znajdują się w domyślnych plikach dziennika śledzenia:

DECLARE @path NVARCHAR(260);

SELECT 
   @path = REVERSE(SUBSTRING(REVERSE([path]), 
   CHARINDEX(CHAR(92), REVERSE([path])), 260)) + N'log.trc'
FROM    sys.traces
WHERE   is_default = 1;

SELECT 
   DatabaseName,
   [FileName],
   SPID,
   Duration,
   StartTime,
   EndTime,
   FileType = CASE EventClass WHEN 92 THEN 'Data' ELSE 'Log' END
FROM sys.fn_trace_gettable(@path, DEFAULT)
WHERE EventClass IN (92,93)
AND DatabaseName = N'AdventureWorks'
ORDER BY StartTime DESC;

Pytanie: Kto i kiedy usunął tabelę dbo.EmployeeAuditData?

Zwróci to wszelkie DROPzdarzenia dla obiektu o nazwie EmployeeAuditData. Jeśli chcesz mieć pewność, że wykrywa on tylko DROPzdarzenia dla tabel, możesz dodać filtr: ObjectType = 8277( pełna lista jest tutaj udokumentowana ). Jeśli chcesz ograniczyć przestrzeń wyszukiwania do konkretnej bazy danych, można dodać filtr: DatabaseName = N'db_name'.

DECLARE @path NVARCHAR(260);

SELECT 
   @path = REVERSE(SUBSTRING(REVERSE([path]), 
   CHARINDEX(CHAR(92), REVERSE([path])), 260)) + N'log.trc'
FROM    sys.traces
WHERE   is_default = 1;

SELECT 
  LoginName,
  HostName,
  StartTime,
  ObjectName,
  TextData
FROM sys.fn_trace_gettable(@path, DEFAULT)
WHERE EventClass = 47    -- Object:Deleted
AND EventSubClass = 1
AND ObjectName = N'EmployeeAuditData'
ORDER BY StartTime DESC;

Jest tu komplikacja i jest to bardzo ostry przypadek, ale uważam, że rozsądnie jest o tym wspomnieć. Jeśli używasz wielu schematów i możesz mieć tę samą nazwę obiektu w wielu schematach, nie będziesz w stanie stwierdzić, który to jest schemat (chyba że jego odpowiedniki nadal istnieją). Istnieje przypadek zewnętrzny, że Użytkownik A mógł upuścić SchemaB.Tablename, podczas gdy Użytkownik B mógł upuścić SchemaA.Tablename. Domyślny ślad nie śledzi schematu obiektu (ani nie przechwytuje TextDatadla tego zdarzenia), aniObjectIDuwzględnienie śledzenia nie jest przydatne do bezpośredniego dopasowania (ponieważ obiekt został upuszczony i już nie istnieje). Uwzględnienie tej kolumny w danych wyjściowych w tym przypadku może być przydatne do odsyłania do dowolnych kopii tabeli o tej samej nazwie, które nadal istnieją, ale jeśli system jest w tak dużym nieładzie (lub jeśli wszystkie takie kopie zostały usunięte) wciąż może nie być wiarygodnym sposobem na odgadnięcie, która kopia tabeli została upuszczona przez kogo.

Rozszerzone wydarzenia

Z obsługi SQL Server 2008: Sesja system_health (blog SQLCSS) , poniżej znajduje się lista danych, które można wyrzucić z system_healthsesji w SQL Server 2008 i 2008 R2:

  • Tekst_sql i identyfikator_sesji dla każdej sesji, w której występuje błąd o istotności> = 20
  • Tekst_sql i identyfikator_sesji dla każdej sesji, w której występuje błąd typu „pamięć”, taki jak 17803, 701 itd. (Dodaliśmy to, ponieważ nie wszystkie błędy pamięci mają istotność> = 20)
  • Zapis wszelkich problemów z „niewydajnością” (czasami widziałeś je w ERRORLOG jako Msg 17883)
  • Wykryte zakleszczenia
  • Callstack, sql_text i session_id dla wszystkich sesji, które czekały na zatrzaski (lub inne ciekawe zasoby) przez> 15 sekund
  • Callstack, sql_text i session_id dla wszystkich sesji, które czekały na blokady przez> 30 sekund
  • Callstack, sql_text i session_id dla każdej sesji, która czekała przez dłuższy czas na „zewnętrzne” oczekiwania lub „wyprzedzające oczekiwania”.

Od Użyj sesji zdarzeń system_health (MSDN) , lista jest nieco rozszerzona w SQL Server 2012 (i pozostaje taka sama dla SQL Server 2014):

  • Tekst_sql i identyfikator_sesji dla każdej sesji, w której występuje błąd o istotności> = 20.
  • Tekst_sql i identyfikator_sesji dla każdej sesji, która napotka błąd związany z pamięcią. Błędy obejmują 17803, 701, 802, 8645, 8651, 8657 i 8902.
  • Rejestr wszelkich nierentownych problemów z planistą. (Są one wyświetlane w dzienniku błędów programu SQL Server jako błąd 17883.)
  • Wykryte zakleszczenia.
  • Callstack, sql_text i session_id dla wszystkich sesji, które czekały na zatrzaski (lub inne interesujące zasoby) przez> 15 sekund.
  • Callstack, sql_text i session_id dla wszystkich sesji, które czekały na blokady przez> 30 sekund.
  • Callstack, sql_text i session_id dla wszystkich sesji, które długo czekały na zapobiegawcze oczekiwania. Czas trwania zależy od typu oczekiwania. Zapobiegawcze oczekiwanie to miejsce, w którym SQL Server czeka na zewnętrzne wywołania API.
  • Callstack i session_id dla alokacji CLR i błędów alokacji wirtualnej.
  • Zdarzenia ring_buffer dla brokera pamięci, monitora harmonogramu, OOM węzła pamięci, bezpieczeństwa i łączności.
  • Składnik systemu wynika z sp_server_diagnostics.
  • Zdrowie instancji zebrane przez scheduler_monitor_system_health_ring_buffer_recorded.
  • Błędy alokacji CLR.
  • Błędy łączności przy użyciu connectivity_ring_buffer_recorded.
  • Błędy bezpieczeństwa przy użyciu security_error_ring_buffer_recorded.

W SQL Server 2016 przechwytywane są jeszcze dwa zdarzenia:

  • Gdy proces zostanie zabity przy użyciu KILLpolecenia.
  • Kiedy SQL Server został zamknięty.

(Dokumentacja nie została jeszcze zaktualizowana, ale blogowałem o tym, jak odkryłem te i inne zmiany ).

Aby uzyskać bardziej tajemniczą konfigurację odpowiednią dla konkretnej wersji, zawsze możesz bezpośrednio uruchomić następujące zapytanie, ale musisz zinterpretować nazwy i przeanalizować predykaty, aby dopasować je do bardziej naturalnych list językowych powyżej:

SELECT e.package, e.event_id, e.name, e.predicate
  FROM sys.server_event_session_events AS e
  INNER JOIN sys.server_event_sessions AS s
  ON e.event_session_id = s.event_session_id
 WHERE s.name = N'system_health'
 ORDER BY e.package, e.name;

Jeśli korzystasz z grup dostępności, uruchomione zostaną również dwie nowe sesje: AlwaysOn_failoveri AlwaysOn_health. Możesz zobaczyć dane, które gromadzą za pomocą następującego zapytania:

SELECT s.name, e.package, e.event_id, e.name, e.predicate
  FROM sys.server_event_session_events AS e
  INNER JOIN sys.server_event_sessions AS s
  ON e.event_session_id = s.event_session_id
 WHERE s.name LIKE N'AlwaysOn[_]%'
 ORDER BY s.name, e.package, e.name;

Te sesje zdarzeń używają docelowych buforów pierścieniowych do przechowywania danych, więc - podobnie jak pula buforów i pamięć podręczna planu - starsze zdarzenia zostaną wycofane, więc niekoniecznie będziesz w stanie wyciągnąć zdarzenia z żądanego zakresu dat.

Przykład

W pytaniu postawiłem to fikcyjne pytanie:

Ile błędów związanych z pamięcią się dzisiaj wydarzyło?

Oto przykładowe (i prawdopodobnie niezbyt wydajne) zapytanie, które może pobrać te informacje z system_healthsesji:

;WITH src(x) AS
(
  SELECT y.query('.')
  FROM
  (
    SELECT x = CONVERT(XML, t.target_data)
      FROM sys.dm_xe_sessions AS s
      INNER JOIN sys.dm_xe_session_targets AS t
      ON s.[address] = t.event_session_address
      WHERE s.name = N'system_health'
  ) AS x
  CROSS APPLY x.x.nodes('/RingBufferTarget/event') AS y(y)
)
SELECT 
  x, ts = CONVERT(DATETIME, NULL), err = CONVERT(INT, NULL)
INTO #blat FROM src;

DELETE #blat WHERE x.value('(/event/@name)[1]', 'varchar(255)') <> 'error_reported';

UPDATE #blat SET ts = x.value('(/event/@timestamp)[1]', 'datetime');

UPDATE #blat SET err = x.value('(/event/data/value)[1]', 'int');

SELECT err, number_of_events = COUNT(*)
  FROM #blat
  WHERE err IN (17803, 701, 802, 8645, 8651, 8657, 8902)
  AND ts >= CONVERT(DATE, CURRENT_TIMESTAMP)
  GROUP BY err;

DROP TABLE #blat;

(Ten przykład pożycza luźno od wstępnego posta na blogu Amit Banerjee na temat system_healthsesji ).

Aby uzyskać więcej informacji na temat wydarzeń rozszerzonych (w tym wielu przykładów, w których można wyszukiwać określone dane), zobacz 31-częściowy blog autorstwa Jonathana Kehayiasa:

https://www.sqlskills.com/blogs/jonathan/an-xevent-a-day-31-days-of-extended-events/

Dziennik błędów

Program SQL Server domyślnie przechowuje bieżące plus 6 najnowszych plików dziennika błędów (ale można to zmienić ). Jest tam przechowywanych wiele informacji, w tym informacje o starcie (ile rdzeni jest używanych, czy ustawione są blokady stron w pamięci, tryb uwierzytelniania itp.), A także błędy i inne scenariusze wystarczająco poważne, aby je udokumentować (i nie można ich przechwycić w innym miejscu). Jednym z ostatnich przykładów był ktoś, kto szuka bazy danych w trybie offline. Możesz to ustalić, skanując tekst w każdym z 7 ostatnich dzienników błędów Setting database option OFFLINE:

EXEC sys.sp_readerrorlog 0,1,'Setting database option OFFLINE';
EXEC sys.sp_readerrorlog 1,1,'Setting database option OFFLINE';
EXEC sys.sp_readerrorlog 2,1,'Setting database option OFFLINE';
EXEC sys.sp_readerrorlog 3,1,'Setting database option OFFLINE';
EXEC sys.sp_readerrorlog 4,1,'Setting database option OFFLINE';
EXEC sys.sp_readerrorlog 5,1,'Setting database option OFFLINE';
EXEC sys.sp_readerrorlog 6,1,'Setting database option OFFLINE';

W tej ostatniej odpowiedzi opisałem kilka innych szczegółów , a także toadworld, a także oficjalna dokumentacja .

Jedna grupa „błędów”, które domyślnie śledzi dziennik błędów - i może znacznie przyspieszyć zrzucanie ważnych informacji - to każda udana kopia zapasowa. Możesz zapobiec wypełnianiu dziennika błędów szumem, włączając flagę śledzenia 3226 .

Aaron Bertrand
źródło