2016-05-11 6 views
1

Es gibt eine Tabelle (SQL Server 2008 R2), die das Up/Down-Protokoll für mehrere Server enthält. Die Server werden in regelmäßigen Abständen gepingt und ihr Status (hoch oder runter) wird in diese Tabelle geschrieben. Es hat eine Struktur wie folgt aus:Ermitteln der Downtime-Dauer in der Serverprotokolltabelle

CREATE TABLE StatusLog 
(
    LogID INT PRIMARY KEY, 
    ServerID INT, 
    QueryDate DATETIME, 
    ServerStatus VARCHAR(50) 
) 

Beispieldaten

INSERT INTO StatusLog 
VALUES 
(1, '1724', '2016-04-16 09:28:00.000', 'up'), 
(2, '1724', '2016-04-16 09:29:00.000', 'up'), 
(3, '1724', '2016-04-16 09:30:00.000', 'down'), 
(6, '1724', '2016-04-16 09:31:00.000', 'down'), 
(8, '1724', '2016-04-16 09:32:00.000', 'down'), 
(9, '1724', '2016-04-16 09:33:00.000', 'down'), 
(17, '1724', '2016-04-16 09:33:40.000', 'up'), 
(18, '1724', '2016-04-16 09:34:00.000', 'up') 

Ich versuche Gesamtausfallzeit für einen bestimmten Server für einen bestimmten Zeitraum zu finden. Im obigen Datenextrakt wird der Status des Servers mit der ID 1724 um 09:30:00 "down" und wechselt um 09:33:40 Uhr zurück zu "up", was einer Gesamtausfallzeit von 220 Sekunden entspricht.

Mein Ansatz ist:

  1. Für jeden "down-Block", finden die "down" Aufzeichnungen und setzen ihre QueryDate als nach unten Startzeit in einer neuen Spalte. Das ist schnell.
  2. Suchen Sie in einer weiteren neuen Spalte den ersten "nach oben" -Datensatz nach dieser Startzeit und legen Sie das QueryDate als Ende der Ausfallzeit fest. Das ist ziemlich schnell.
  3. Tun Sie dies jedoch nur für den ersten Down Record im Down Block und nicht für andere Downs im Down Block, sonst berechnen Sie die Ausfallzeit mehrfach falsch. Um dies zu tun, muss ich mir die Zeilennummern ansehen und hier werden die Dinge unordentlich und langsam.
  4. Schließlich extrahieren Sie sie voneinander und Sie haben Ausfallzeit für diesen Block
  5. Summe alle Down-Zeiten, um die gesamte Stillstandszeit zu finden.

Ich schrieb die unten stehende Skript, aber es ist schrecklich langsam ist (Jeder Server Hunderttausende von Protokollaufzeichnungen hat)

DECLARE @StartDate DATE = '2016-04-01' 
DECLARE @EndDate DATE = '2016-04-30' 
DECLARE @ServerID INT = '1724' 

;WITH CTE_StatusLog AS 
(
SELECT LogID, QueryDate, ServerStatus, 
    ROW_NUMBER() OVER (ORDER BY QueryDate) AS RN 
FROM StatusLog 
WHERE ServerID = @ServerID 
    AND QueryDate BETWEEN @StartDate AND @EndDate 
) 

SELECT LogID, 
     QueryDate, 
     ServerStatus, 
     RN, 
     DownStarted = CASE WHEN s1.ServerStatus = 'down' 
          THEN s1.QueryDate END, 
     DownEnded = (SELECT TOP 1 QueryDate 
        FROM CTE_StatusLog AS s2 
        WHERE s2.QueryDate > s1.QueryDate 
        AND s1.ServerStatus = 'down' 
        AND s2.ServerStatus = 'up' 
        AND (SELECT s3.ServerStatus 
        FROM CTE_StatusLog AS s3 
        WHERE s3.RN = s1.RN-1) <> 'down' 
       ORDER BY s2.QueryDate), 
     DownDuration = DATEDIFF(SECOND, 
       CASE WHEN s1.ServerStatus = 'down' 
        THEN s1.QueryDate END, 
       (SELECT TOP 1 QueryDate 
       FROM CTE_StatusLog AS s2 
       WHERE s2.QueryDate > s1.QueryDate 
       AND s1.ServerStatus = 'down' 
       AND s2.ServerStatus = 'up' 
       AND (SELECT s3.ServerStatus 
        FROM CTE_StatusLog AS s3 
        WHERE s3.RN = s1.RN-1) <> 'down' 
       ORDER BY s2.QueryDate)) 
FROM CTE_StatusLog AS s1 
WHERE QueryDate BETWEEN @StartDate AND @EndDate 
ORDER BY s1.RN 

Der Ausgang:

LogID QueryDate    ServerStatus RN DownStarted    DownEnded    DownDuration 
1  2016-04-16 09:28:00.000 up    1 NULL     NULL     NULL 
2  2016-04-16 09:29:00.000 up    2 NULL     NULL     NULL 
3  2016-04-16 09:30:00.000 down   3 2016-04-16 09:30:00.000 2016-04-16 09:33:40.000 220 
6  2016-04-16 09:31:00.000 down   4 2016-04-16 09:31:00.000 NULL     NULL 
8  2016-04-16 09:32:00.000 down   5 2016-04-16 09:32:00.000 NULL     NULL 
9  2016-04-16 09:33:00.000 down   6 2016-04-16 09:33:00.000 NULL     NULL 
17  2016-04-16 09:33:40.000 up    7 NULL     NULL     NULL 
18  2016-04-16 09:34:00.000 up    8 NULL     NULL     NULL 

Wie kann ich dies verbessern Skript oder gibt es eine bessere Möglichkeit, Ausfallzeiten in Bezug auf diese Tabellenstruktur zu berechnen?

Antwort

1

Ich würde dies nähern, indem ich die nächste up-Zeit für jeden down-Datensatz bekommen.In SQL Server 2008, das verwendet outer apply:

select sl.*, slup.querydate as next_update, 
     datediff(second, sl.querydate, slup.querydate) as down_in_seconds 
from statuslog sl outer apply 
    (select top 1 sl2.* 
     from statuslog sl2 
     where sl2.serverid = sl.serverid and 
      sl2.querydate >= sl.querydate and 
      sl2.serverstatus = 'up' 
     order by sl2.querydate asc 
    ) slup 
where sl.serverstatus = 'down'; 

Wenn Sie eine Zusammenfassung von Ausfallzeiten wollen, dann würde ich Aggregation verwenden:

select servid, min(querydate) as down_date, next_update, 
     max(down_in_seconds) 
from (select sl.*, slup.querydate as next_update, 
      datediff(second, sl.querydate, slup.querydate) as down_in_seconds 
     from statuslog sl outer apply 
      (select top 1 sl2.* 
      from statuslog sl2 
      where sl2.serverid = sl.serverid and 
        sl2.querydate >= sl.querydate and 
        sl2.serverstatus = 'up' 
      order by sl2.querydate asc 
      ) slup 
     where sl.serverstatus = 'down' 
    ) slud 
group by serverid, next_update; 
+0

Schöne Antwort. Ich denke, du musst die Gruppe um down_in_seconds entfernen und stattdessen das mit einem 'MAX (...)' –

+0

@JamieF aggregieren. . . Ja. Das nächste Datum kann in der "Gruppe von" sein, aber die zweite Änderung in jeder Zeile. Du hast recht. –

+0

Äußere Anwendung half sehr, danke. –

1

Wenn Sie nur die Gesamtausfallzeit benötigen, können Sie herausfinden, was jede Zeile darstellt: Nehmen wir an, jede nach unten stehende Zeile gibt die Sekunden der Ausfallzeit seit der letzten Überprüfung dieses Servers an. Dann die Zeilen Summe:

DECLARE @StartDate DATE = '2016-04-01' 
DECLARE @EndDate DATE = '2016-04-30' 
DECLARE @ServerID INT = '1724' 

SELECT 
individualRows.ServerId, 
individualRows.ServerStatus, 
SUM(secondsInState) AS TotalTime 
FROM 
(Select 
statusLog.ServerId, 
statusLog.QueryDate, 
statusLog.ServerStatus, 
DateDiff(second, PreviousStatus.QueryDate, statusLog.QueryDate) as secondsInState 
FROM 
StatusLog 
left outer join 
StatusLog AS PreviousStatus 
ON StatusLog.ServerId = PreviousStatus.ServerId 
AND PreviousStatus.QueryDate < StatusLog.QueryDate 
AND PreviousStatus.QueryDate = (SELECT Max(QueryDate) FROM statusLog sl2 where sl2.ServerId= StatusLog.ServerId and sl2.QueryDate < StatusLog.QueryDate) 
WHERE StatusLog.QueryDate > @StartDate 
AND StatusLog.QueryDate < @EndDate 
AND StatusLog.ServerId = @ServerID) AS individualRows 
GROUP BY 
individualRows.ServerId, 
individualRows.ServerStatus 

Wenn Sie wirklich die Zeit für jeden Ausfall brauchen, könnte ich eine temporäre Tabelle versuchen, mit jeder Zeile mit der vorherige Reihe sowie die vorherige Reihe in dem entgegengesetzten Zustand verbunden. Ähnlich wie Ihre Ergebnisse. Dann würde ich diese temporäre Tabelle filtern und aggregieren.

Meine Erfahrung ist, dass temporäre Tabellen viel schneller als CTEs sind, sobald die Tabelle viele Datenzeilen erhält.

+0

Sie haben Recht über die temporären Tabellen, ich wollte gehen Mit ihnen war Gordon's Ansatz jedoch ausreichend. Vielen Dank. –