sql >> Database >  >> RDS >> Database

Het standaardspoor verwijderen - Deel 2

[ Deel 1 | Deel 2 | Deel 3 ]

In de eerste post in deze serie liet ik de analyse zien die ik gebruikte om te bepalen dat de standaardtracering niet voor ons is. Terwijl ik keek welke informatie we eigenlijk in plaats daarvan moesten verzamelen (wijzigingen van de bestandsgrootte) en hoe dit aan gebruikers zou moeten worden getoond, overwoog ik de volgende punten over de standaardtracering:

  • het legt alleen automatisch vast evenementen;
  • het legt niet de "boosdoener"-batch vast die de gebeurtenis heeft veroorzaakt, tenzij je het geluk hebt dat het ook om een ​​andere reden is vastgelegd (bijv. DDL); en,
  • het legt gebeurtenissen vast met lokale tijd (onze servers zijn Oosters en houden zich aan de DST).

Ter verdediging legt het veel belangrijke informatie over die automatische gebeurtenissen vast. Nadat we de standaardtracering hebben uitgeschakeld, willen we misschien nog steeds gebeurtenissen bekijken die vóór de wijziging hebben plaatsgevonden en in die bestanden zijn vastgelegd. Maar zodra de standaardtracering is uitgeschakeld, bestaat de rij niet meer in sys.traces , dus u kunt het pad naar de .trc . niet bepalen bestanden van daaruit. Hier is waar de inflexibiliteit van de standaardtracering een voordeel biedt:de bestanden zijn hard gecodeerd om zich in dezelfde map te bevinden als SERVERPROPERTY(N'ErrorLogFileName') . Dus zelfs als de standaardtracering is uitgeschakeld, kunnen we nog steeds gegevens uit de bestanden halen met behulp van de volgende query (met aanpassingen om de gegevens in UTC weer te geven):

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

Voorbeeldresultaten van één server, waar zeker enkele handmatige en automatische gebeurtenissen plaatsvonden (klik om te vergroten):

Een vervanging schrijven

De Extended Events-sessie die ik heb opgesteld om dit te vervangen, die ook handmatige wijzigingen in de bestandsgrootte en de querytekst die automatische gebeurtenissen veroorzaakte, zou vastleggen, is als volgt:

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;

Terwijl het lijkt op username en server_principal_name overbodig kan zijn, heb ik gevallen gevonden waarin de laatste NULL . was (en het lijkt erop dat dit probleem al een tijdje bestaat).

De resultaten controleren

Ik heb die sessie op 22 februari ingeschakeld, dus het mist de gebeurtenissen die het standaardspoor op de 12e heeft vastgelegd, maar het heeft meer vastgelegd dan de enkele autogroeigebeurtenis van de 23e. Ik heb de volgende query uitgevoerd om een ​​resultaat te krijgen met dezelfde vorm als hierboven:

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

De resultaten tonen het extra plezier dat ik had, inclusief (hijg!) het uitvoeren van een "Shrink Database" -taak vanuit de gebruikersinterface (klik om te vergroten):

Overal inzetten

Ik had er vertrouwen in dat ik nu een completer beeld kon krijgen van de veranderingen in de bestandsgrootte op elke server, dus het was tijd om te implementeren. Ik heb een CMS-queryvenster gebruikt om eerst overal de standaardtracering uit te schakelen en de show advanced options in te stellen terug zoals ik het vond:

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

Om vervolgens de Extended Event-sessie te maken, moet ik dynamische SQL gebruiken, omdat sommige servers mogelijk verschillende paden hebben voor SERVERPROPERTY(N'ErrorLogFileName') en dat argument kan niet worden geparametriseerd.

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;

Het bewijs zit in de pudding

Ik heb een schijnwerkbelasting gemaakt die opzettelijk zwaar was met dingen die gebeurtenissen naar de standaardtracering zouden loggen, en deze vervolgens meerdere keren uitgevoerd met en zonder de standaardtracering ingeschakeld, om te laten zien dat het waarnemerseffect een impact kan hebben op de werkbelasting.

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

Gemiddelde looptijd:

Standaard traceren Gemiddelde werkbelasting
Ingeschakeld 147.4s
Uitgeschakeld 131.6s

Een runtime-reductie van 10-11% is op zich zeker niet enorm, maar het is een grote overwinning als je nadenkt over de cumulatieve impact op een hele vloot van 200+ servers.

Wat nu?

Doe dit nog niet . We moeten nog praten over enkele bijwerkingen van het uitschakelen van de standaardtracering en weergaven maken zodat gebruikers de sessiegegevens gemakkelijk kunnen gebruiken zonder XQuery-experts te worden. Blijf op de hoogte!

[ Deel 1 | Deel 2 | Deel 3 ]


  1. Hoe kan ik een SQL Server-verbindingsreeks instellen?

  2. Splits een partitie in tweeën in SQL Server (T-SQL)

  3. Onbekende kolom in 'veldlijst'-fout op MySQL Update-query

  4. Een PostgreSQL-reeks naar een veld maken (wat niet de ID van de record is)