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

Usuwanie domyślnego śladu – część 2

[ Część 1 | Część 2 | Część 3 ]

W pierwszym poście z tej serii pokazałem analizę, której użyłem do ustalenia, że ​​domyślny ślad nie jest dla nas. Patrząc na to, jakie informacje faktycznie musieliśmy zebrać w jego miejscu (zmiany rozmiaru pliku) i jak należy to ujawnić użytkownikom, rozważyłem następujące punkty dotyczące domyślnego śledzenia:

  • przechwytuje tylko automatycznie wydarzenia;
  • nie przechwytuje partii „winowajcy”, która spowodowała zdarzenie, chyba że masz szczęście, że została ona przechwycona również z innego powodu (np. DDL); oraz,
  • przechwytuje zdarzenia w czasie lokalnym (nasze serwery są wschodnie i przestrzegają czasu letniego).

W swojej obronie przechwytuje wiele ważnych informacji o tych automatycznych zdarzeniach. Po wyłączeniu śledzenia domyślnego możemy nadal chcieć przejrzeć zdarzenia, które miały miejsce przed zmianą i zostały przechwycone w tych plikach. Ale po wyłączeniu domyślnego śledzenia wiersz nie istnieje już w sys.traces , więc nie możesz określić ścieżki do pliku .trc pliki stamtąd. W tym miejscu brak elastyczności domyślnego śledzenia faktycznie zapewnia korzyści:pliki są zakodowane na stałe w tym samym folderze, co SERVERPROPERTY(N'ErrorLogFileName') . Tak więc, nawet jeśli domyślne śledzenie jest wyłączone, nadal możemy pobierać dane z plików za pomocą następującego zapytania (z korektami, aby wyświetlić dane w UTC):

;WITH dst AS
(
    SELECT s,e,d 
      FROM (VALUES ('20190310','20191103',240),('20191103','20200308',300),
                   ('20200308','20201101',240),('20201101','20210314',300),
                   ('20210314','20211107',240)) AS dst(s,e,d)
),    -- will add 2022, 2023, etc. later (if DST is still a thing then)
p AS
(
 
    SELECT TracePath = REVERSE(SUBSTRING(p, CHARINDEX(N'\', p), 260)) + N'log.trc' FROM 
    (SELECT REVERSE((CONVERT(nvarchar(max), SERVERPROPERTY(N'ErrorLogFileName'))))) AS s(p)
), 
trc AS
(
    SELECT src = 'trc', 
      t.DatabaseName, 
      t.[FileName], 
      DurationSeconds = CONVERT(decimal(18,3),Duration/1000000.0),
      StartTimeUTC = DATEADD(MINUTE, COALESCE(st1.d,0), t.StartTime),
      EndTimeUTC   = DATEADD(MINUTE, COALESCE(st2.d,0), t.EndTime),
      FileType = CASE t.EventClass WHEN 92 THEN 'Data' WHEN 93 THEN 'Log' END,
      Culprit = TextData, 
      IsAutomatic = 'true', 
      ChangeMB = CONVERT(bigint, IntegerData)*8/1024, 
      Principal = t.LoginName, 
      t.HostName, 
      App = CASE WHEN ApplicationName LIKE N'%Management Studio%Query%' 
                      THEN N'SSMS - Query Window'
                 WHEN ApplicationName LIKE N'%Management Studio%'
                      THEN N'SSMS - GUI!'
                 ELSE ApplicationName END
    FROM p CROSS APPLY sys.fn_trace_gettable(p.TracePath, DEFAULT) AS t
    LEFT OUTER JOIN dst AS st1 ON  t.StartTime >= DATEADD(HOUR,2,st1.s) 
                               AND t.StartTime <  DATEADD(HOUR,2,st1.e)
    LEFT OUTER JOIN dst AS st2 ON  t.EndTime   >= DATEADD(HOUR,2,st2.s) 
                               AND t.EndTime   <  DATEADD(HOUR,2,st2.e)
    WHERE t.EventClass IN (92,93)
)
SELECT * 
  FROM trc
  ORDER BY StartTimeUTC DESC;

Przykładowe wyniki z jednego serwera, na którym na pewno miały miejsce zdarzenia ręczne i automatyczne (kliknij, aby powiększyć):

Pisanie zamiennika

Sesja Extended Events, którą sformułowałem, aby to zastąpić, która obejmowałaby również ręczne zmiany rozmiaru pliku i tekst zapytania, który powodował automatyczne zdarzenia, jest następująca:

CREATE EVENT SESSION FileSizeChanges ON SERVER 
ADD EVENT sqlserver.database_file_size_change
(
  ACTION
  (
    sqlserver.sql_text,
    sqlserver.client_app_name,
    sqlserver.client_hostname,
    sqlserver.username,
    sqlserver.server_principal_name
  )
)
ADD TARGET package0.event_file
(
  SET filename       = N'W:\SomePath\FileSizeChanges.xel',
  MAX_FILE_SIZE      = 100, -- MB
  MAX_ROLLOVER_FILES = 100  -- 100 MB x 100 = max 10 GB
)
WITH
(
  MAX_MEMORY            = 8192 KB,
  EVENT_RETENTION_MODE  = ALLOW_SINGLE_EVENT_LOSS,
  MAX_DISPATCH_LATENCY  = 30 SECONDS,
  MAX_EVENT_SIZE        = 0 KB,
  TRACK_CAUSALITY       = OFF,
  STARTUP_STATE         = ON
);
 
ALTER EVENT SESSION FileSizeChanges ON SERVER STATE = START;

Podczas gdy wygląda jak username i server_principal_name może być zbędne, faktycznie znalazłem przypadki, w których ten ostatni był NULL (i wygląda na to, że ten problem istnieje już od jakiegoś czasu).

Sprawdzanie wyników

Włączyłem tę sesję 22 lutego, więc brakuje w niej zdarzeń, które domyślny ślad przechwycił 12 lutego, ale przechwycił więcej niż jedno zdarzenie autowzrostu z 23 lutego. Uruchomiłem następujące zapytanie, aby uzyskać wynik o takim samym kształcie jak powyżej:

;WITH FileInfo(XEPath) AS
(
  SELECT LEFT(BasePath,COALESCE(NULLIF(CHARINDEX(SessionName,BasePath)-1,-1),0)) 
         + SessionName + N'*.xel' 
    FROM
    (
      SELECT xmlsrc.data.value(N'(@name)[1]', N'nvarchar(max)'), SessionName
        FROM 
        (
          SELECT CONVERT(xml,target_data), s.[name]
            FROM sys.dm_xe_session_targets AS t
            INNER JOIN sys.dm_xe_sessions AS s
            ON s.[address] = t.event_session_address
            WHERE s.[name] = N'FileSizeChanges'
        ) AS xefile (TargetData, SessionName)
        CROSS APPLY TargetData.nodes(N'//EventFileTarget/File') AS xmlsrc(data)
    ) AS InnerData(BasePath, SessionName)
),
SessionData(EventData) AS 
(
  SELECT CONVERT(xml, TargetData.event_data) FROM FileInfo
  CROSS APPLY sys.fn_xe_file_target_read_file(FileInfo.XEPath, NULL, NULL, NULL) AS TargetData
), 
src AS
(
  SELECT 
    EndTimeUTC   = x.d.value(N'(@timestamp)[1]', N'datetime2'),
    DatabaseID   = x.d.value(N'(data  [@name="database_id"]/value)[1]',           N'int'),
    [FileName]   = x.d.value(N'(data  [@name="file_name"]/value)[1]',             N'sysname'),
    Duration     = x.d.value(N'(data  [@name="duration"]/value)[1]',              N'int'),
    FileType     = x.d.value(N'(data  [@name="file_type"]/text)[1]',              N'varchar(4)'),
    Culprit      = x.d.value(N'(action[@name="sql_text"]/value)[1]',              N'nvarchar(max)'),
    IsAutomatic  = x.d.value(N'(data  [@name="is_automatic"]/value)[1]',          N'varchar(5)'),
    ChangeKB     = x.d.value(N'(data  [@name="size_change_kb"]/value)[1]',        N'bigint'),
    Principal    = x.d.value(N'(action[@name="server_principal_name"]/value)[1]', N'sysname'),
    username     = x.d.value(N'(action[@name="username"]/value)[1]',              N'sysname'),
    AppName      = x.d.value(N'(action[@name="client_app_name"]/value)[1]',       N'sysname'),
    HostName     = x.d.value(N'(action[@name="client_hostname"]/value)[1]',       N'sysname')
  FROM SessionData CROSS APPLY EventData.nodes(N'/event') AS x(d)
)
SELECT 
  src = 'xe', 
  DatabaseName    = DB_NAME(DatabaseID), 
  [FileName], 
  DurationSeconds = CONVERT(decimal(18,3), Duration/1000000.0),
  StartTimeUTC    = DATEADD(MICROSECOND, -Duration, EndTimeUTC), 
  EndTimeUTC,
  FileType, 
  Culprit, 
  IsAutomatic, 
  ChangeMB  = CONVERT(decimal(18,3), ChangeKB / 1024.0), 
  Principal = COALESCE([Principal], COALESCE(NULLIF(username,N''), N'?')),
  HostName, 
  App = CASE WHEN AppName LIKE N'%Management Studio%Query%' 
                  THEN N'SSMS - Query Window'
             WHEN AppName LIKE N'%Management Studio%'       
                  THEN N'SSMS - GUI!'
             ELSE AppName END
FROM src
ORDER BY StartTimeUTC DESC;

Wyniki pokazują dodatkową zabawę, jaką miałem, w tym (wzdycha!) uruchamiając zadanie „Shrink Database” z interfejsu użytkownika (kliknij, aby powiększyć):

Wdrażaj wszędzie

Przekonany, że mogę teraz uzyskać pełniejszy obraz zdarzeń zmiany rozmiaru plików na dowolnym serwerze, nadszedł czas na wdrożenie. Użyłem okna zapytania CMS, aby najpierw wyłączyć domyślny ślad wszędzie i ustawić show advanced options z powrotem tak, jak go znalazłem:

IF EXISTS 
(
  SELECT 1 FROM sys.configurations 
    WHERE name = N'default trace enabled' 
    AND value_in_use = 1
)
BEGIN
  DECLARE @OriginalAdvancedOptions bit = 
  (
    SELECT CONVERT(bit, value_in_use)
      FROM sys.configurations 
      WHERE name = N'show advanced options'
  );
 
  IF @OriginalAdvancedOptions = 0 -- need to turn this on if it's not already
  BEGIN
    EXEC sys.sp_configure @configname = N'show advanced options', @configvalue = 1;
    RECONFIGURE WITH OVERRIDE;
  END
 
  EXEC   sys.sp_configure @configname = N'default trace enabled', @configvalue = 0;
 
  IF @OriginalAdvancedOptions = 0 -- need to set it back (else leave it)
  BEGIN
    EXEC sys.sp_configure @configname = N'show advanced options', @configvalue = 0;
  END
 
  RECONFIGURE WITH OVERRIDE;
END
GO

Następnie, aby utworzyć sesję Extended Event, muszę użyć dynamicznego SQL, ponieważ niektóre serwery mogą mieć różne ścieżki dla SERVERPROPERTY(N'ErrorLogFileName') a ten argument nie może być sparametryzowany.

DECLARE @path nvarchar(max) = (SELECT REVERSE(SUBSTRING(p, CHARINDEX(N'\', p), 4000)) 
  FROM (SELECT REVERSE((CONVERT(nvarchar(max), SERVERPROPERTY(N'ErrorLogFileName'))))) AS s(p));
 
IF EXISTS (SELECT 1 FROM sys.dm_xe_sessions WHERE name = N'FileSizeChanges')
BEGIN
  EXEC sys.sp_executesql N'DROP EVENT SESSION FileSizeChanges ON SERVER;';
END
 
DECLARE @sql nvarchar(max) = N' CREATE EVENT SESSION FileSizeChanges ON SERVER 
ADD EVENT sqlserver.database_file_size_change
(
  ACTION
  (
    sqlserver.sql_text,
    sqlserver.client_app_name,
    sqlserver.client_hostname,
    sqlserver.username,
    sqlserver.server_principal_name
  )
)
ADD TARGET package0.event_file
(
  SET filename       = ''' + @path + N'FileSizeChanges.xel'',
  MAX_FILE_SIZE      = 100, -- MB
  MAX_ROLLOVER_FILES = 100  -- 100 MB x 100 = max 10 GB
)
WITH
(
  MAX_MEMORY            = 8192 KB,
  EVENT_RETENTION_MODE  = ALLOW_SINGLE_EVENT_LOSS,
  MAX_DISPATCH_LATENCY  = 30 SECONDS,
  MAX_EVENT_SIZE        = 0 KB,
  TRACK_CAUSALITY       = OFF,
  STARTUP_STATE         = ON
);
 
ALTER EVENT SESSION FileSizeChanges ON SERVER STATE = START;';
 
EXEC sys.sp_executesql @sql;

Dowód jest w budyniu

Stworzyłem symulowane obciążenie, które było celowo obciążone rzeczami, które rejestrowałyby zdarzenia w domyślnym śledzeniu, a następnie uruchomiłem go wiele razy z włączonym domyślnym śledzeniem i bez niego, aby pokazać, że efekt obserwatora może mieć wpływ na obciążenie.

SELECT [starting] = sysdatetime();
GO
 
EXEC sys.sp_executesql N'CREATE OR ALTER PROCEDURE dbo.dostuff
AS
BEGIN
  SET NOCOUNT ON;
  SELECT DISTINCT TOP (1000) object_id, column_id INTO #blat FROM sys.all_columns;
  ALTER TABLE #blat ADD CONSTRAINT PK_wahoo PRIMARY KEY (object_id, column_id);
  ALTER TABLE #blat ADD CONSTRAINT DF_what DEFAULT(1) FOR object_id;
  CREATE INDEX IX_what ON #blat(column_id);
  DROP TABLE #blat;
END';
 
EXEC dbo.dostuff;
 
CREATE USER smidgen WITHOUT LOGIN;
 
ALTER ROLE db_owner ADD MEMBER smidgen;
 
DBCC TRACEON(2861) WITH NO_INFOMSGS;
DBCC TRACEOFF(2861) WITH NO_INFOMSGS;
 
DROP USER smidgen;
GO 5000
 
SELECT [finished] = sysdatetime();
GO

Średni czas pracy:

Ślad domyślny Średni czas obciążenia
Włączone 147,4s
Wyłączone 131,6s

Skrócenie czasu pracy o 10-11% z pewnością nie jest ogromne w izolacji, ale jest to duża wygrana, jeśli pomyślimy o skumulowanym wpływie na całą flotę ponad 200 serwerów.

Co dalej?

Jeszcze tego nie rób . Nadal musimy mówić o pewnych efektach ubocznych wyłączenia śledzenia domyślnego i tworzyć widoki, aby użytkownicy mogli łatwo korzystać z danych sesji bez zostania ekspertem XQuery. Bądź na bieżąco!

[ Część 1 | Część 2 | Część 3 ]


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Najlepsze blogi dotyczące baz danych do obserwowania

  2. Wgląd w wydajność zapytań:odkrywanie, co zużywa zasoby bazy danych SQL Azure?

  3. Minimalne logowanie za pomocą INSERT…SELECT do tabel sterty

  4. Typy danych SQL:5 najgorszych wyborów, których musisz dzisiaj zatrzymać

  5. Przechowywanie plików w bazie danych SQL przy użyciu FILESTREAM — część 2