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

Sprawa dla ZAMIAST wyzwalaczy – część 1

W zeszłym roku opublikowałem wskazówkę zatytułowaną Popraw wydajność serwera SQL, przełączając się na wyzwalacze ZAMIAST.

Głównym powodem, dla którego preferuję wyzwalacz ZAMIAST, szczególnie w przypadkach, w których spodziewam się wielu naruszeń logiki biznesowej, jest to, że wydaje się intuicyjne, że taniej byłoby całkowicie zapobiec działaniu, niż kontynuować i wykonać je (i zarejestruj to!), tylko po to, aby użyć wyzwalacza PO, aby usunąć nieprawidłowe wiersze (lub wycofać całą operację). Wyniki pokazane w tej wskazówce pokazały, że tak było w rzeczywistości – i podejrzewam, że byłyby jeszcze bardziej widoczne w przypadku większej liczby nieklastrowanych indeksów dotkniętych operacją.

Było to jednak na wolnym dysku i na wczesnym CTP SQL Server 2014. Przygotowując slajd do nowej prezentacji, którą będę robił w tym roku na temat wyzwalaczy, odkryłem, że w nowszej wersji SQL Server 2014 – w połączeniu ze zaktualizowanym sprzętem – trochę trudniej było zademonstrować tę samą różnicę wydajności między wyzwalaczem AFTER i INSTEAD OF. Więc postanowiłem odkryć dlaczego, chociaż od razu wiedziałem, że będzie to więcej pracy, niż kiedykolwiek zrobiłem dla jednego slajdu.

Jedną rzeczą, o której chcę wspomnieć, jest to, że wyzwalacze mogą używać tempdb na różne sposoby, a to może wyjaśniać niektóre z tych różnic. Wyzwalacz AFTER używa magazynu wersji dla wstawionych i usuniętych pseudotabel, podczas gdy wyzwalacz INSTEAD OF tworzy kopię tych danych w wewnętrznej tabeli roboczej. Różnica jest subtelna, ale warta podkreślenia.

Zmienne

Zamierzam przetestować różne scenariusze, w tym:

  • Trzy różne wyzwalacze:
    • Wyzwalacz AFTER, który usuwa określone wiersze, które nie powiodły się
    • Wyzwalacz AFTER, który wycofuje całą transakcję w przypadku niepowodzenia dowolnego wiersza
    • Wyzwalacz ZAMIAST OF, który wstawia tylko wiersze, które przechodzą
  • Różne modele odzyskiwania i ustawienia izolacji migawki:
    • FULL z włączoną funkcją SNAPSHOT
    • FULL z wyłączonym SNAPSHOT
    • PROSTY z włączoną funkcją SNAPSHOT
    • SIMPLE z wyłączonym SNAPSHOT
  • Różne układy dysków*:
    • Dane na dysku SSD, zaloguj się na dysk twardy 7200 obr./min
    • Dane na SSD, zaloguj się na SSD
    • Dane na 7200 obr./min HDD, zaloguj się na SSD
    • Dane na dysku 7200 obr/min, logowanie na dysku 7200 obr/min
  • Różne wskaźniki niepowodzeń:
    • 10%, 25% i 50% wskaźnik niepowodzeń w całym:
      • Wstawianie pojedynczej partii 20 000 wierszy
      • 10 partii po 2000 wierszy
      • 100 partii po 200 rzędów
      • 1000 partii po 20 rzędów
      • 20 000 wkładek singletonowych

    * tempdb to pojedynczy plik danych na wolnym dysku o prędkości 7200 obr./min. Jest to celowe i ma na celu wzmocnienie wszelkich wąskich gardeł spowodowanych różnymi zastosowaniami tempdb . Planuję wrócić do tego testu w pewnym momencie, gdy tempdb jest na szybszym dysku SSD.

OK, TL; DR już!

Jeśli chcesz tylko poznać wyniki, przejdź w dół. Wszystko w środku to tylko tło i wyjaśnienie, jak skonfigurowałem i przeprowadziłem testy. Nie mam złamanego serca, że ​​nie wszyscy będą zainteresowani wszystkimi drobiazgami.

Scenariusz

W przypadku tego konkretnego zestawu testów rzeczywisty scenariusz to taki, w którym użytkownik wybiera nazwę ekranową, a wyzwalacz jest przeznaczony do wychwytywania przypadków, w których wybrana nazwa narusza pewne zasady. Na przykład nie może to być żadna odmiana „Ninny-Muggins” (z pewnością możesz użyć tutaj swojej wyobraźni).

Utworzyłem tabelę z 20 000 unikalnych nazw użytkowników:

USE model;
GO
 
-- 20,000 distinct, good Names
;WITH distinct_Names AS
(
  SELECT Name FROM sys.all_columns
  UNION 
  SELECT Name FROM sys.all_objects
)
SELECT TOP (20000) Name 
INTO dbo.GoodNamesSource
FROM
(
  SELECT Name FROM distinct_Names
  UNION 
  SELECT Name + 'x' FROM distinct_Names
  UNION 
  SELECT Name + 'y' FROM distinct_Names
  UNION 
  SELECT Name + 'z' FROM distinct_Names
) AS x;
 
CREATE UNIQUE CLUSTERED INDEX x ON dbo.GoodNamesSource(Name);

Następnie stworzyłem tabelę, która byłaby źródłem moich „niegrzecznych imion” do sprawdzenia. W tym przypadku jest to po prostu ninny-muggins-00001 przez ninny-muggins-10000 :

USE model;
GO
 
CREATE TABLE dbo.NaughtyUserNames
(
  Name NVARCHAR(255) PRIMARY KEY
);
GO
 
-- 10,000 "bad" names
INSERT dbo.NaughtyUserNames(Name)
  SELECT N'ninny-muggins-' + RIGHT(N'0000' + RTRIM(n),5)
  FROM
  (
    SELECT TOP (10000) n = ROW_NUMBER() OVER (ORDER BY Name)
	FROM dbo.GoodNamesSource
  ) AS x;

Stworzyłem te tabele w model bazy danych, aby za każdym razem, gdy tworzę bazę danych, istniała ona lokalnie, i planuję utworzyć wiele baz danych, aby przetestować macierz scenariuszy wymienioną powyżej (zamiast zmieniać ustawienia bazy danych, czyścić dziennik itp.). Pamiętaj, że jeśli tworzysz obiekty w modelu do celów testowych, upewnij się, że usuniesz te obiekty, gdy skończysz.

Nawiasem mówiąc, zamierzam celowo pominąć naruszenia kluczy i inne obsługę błędów, przyjmując naiwne założenie, że wybrana nazwa jest sprawdzana pod kątem unikalności na długo przed próbą wstawienia, ale w ramach tej samej transakcji (tak jak w przypadku sprawdź z tabelą niegrzecznych imion, które można było zrobić wcześniej).

Aby to wesprzeć, stworzyłem również następujące trzy prawie identyczne tabele w model , do celów izolacji testowej:

USE model;
GO
 
 
-- AFTER (rollback)
CREATE TABLE dbo.UserNames_After_Rollback
(
  UserID INT IDENTITY(1,1) PRIMARY KEY,
  Name NVARCHAR(255) NOT NULL UNIQUE,
  DateCreated DATE NOT NULL DEFAULT SYSDATETIME()
);
CREATE INDEX x ON dbo.UserNames_After_Rollback(DateCreated) INCLUDE(Name);
 
 
-- AFTER (delete)
CREATE TABLE dbo.UserNames_After_Delete
(
  UserID INT IDENTITY(1,1) PRIMARY KEY,
  Name NVARCHAR(255) NOT NULL UNIQUE,
  DateCreated DATE NOT NULL DEFAULT SYSDATETIME()
);
CREATE INDEX x ON dbo.UserNames_After_Delete(DateCreated) INCLUDE(Name);
 
 
-- INSTEAD
CREATE TABLE dbo.UserNames_Instead
(
  UserID INT IDENTITY(1,1) PRIMARY KEY,
  Name NVARCHAR(255) NOT NULL UNIQUE,
  DateCreated DATE NOT NULL DEFAULT SYSDATETIME()
);
CREATE INDEX x ON dbo.UserNames_Instead(DateCreated) INCLUDE(Name);
GO

Oraz następujące trzy wyzwalacze, po jednym dla każdej tabeli:

USE model;
GO
 
 
-- AFTER (rollback)
CREATE TRIGGER dbo.trUserNames_After_Rollback
ON dbo.UserNames_After_Rollback
AFTER INSERT
AS
BEGIN
  SET NOCOUNT ON;
 
  IF EXISTS 
  (
   SELECT 1 FROM inserted AS i
    WHERE EXISTS
    (
      SELECT 1 FROM dbo.NaughtyUserNames
      WHERE Name = i.Name
    )
  )
  BEGIN
    ROLLBACK TRANSACTION;
  END
END
GO
 
 
-- AFTER (delete)
CREATE TRIGGER dbo.trUserNames_After_Delete
ON dbo.UserNames_After_Delete
AFTER INSERT
AS
BEGIN
  SET NOCOUNT ON;
 
  DELETE d
    FROM inserted AS i
    INNER JOIN dbo.NaughtyUserNames AS n
    ON i.Name = n.Name
    INNER JOIN dbo.UserNames_After_Delete AS d
    ON i.UserID = d.UserID;
END
GO
 
 
-- INSTEAD
CREATE TRIGGER dbo.trUserNames_Instead
ON dbo.UserNames_Instead
INSTEAD OF INSERT
AS
BEGIN
  SET NOCOUNT ON;
 
  INSERT dbo.UserNames_Instead(Name)
    SELECT i.Name
      FROM inserted AS i
      WHERE NOT EXISTS
      (
        SELECT 1 FROM dbo.NaughtyUserNames
        WHERE Name = i.Name
      );
END
GO

Prawdopodobnie chciałbyś rozważyć dodatkową obsługę, aby powiadomić użytkownika, że ​​jego wybór został wycofany lub zignorowany – ale to również jest pominięte dla uproszczenia.

Konfiguracja testu

Stworzyłem przykładowe dane reprezentujące trzy wskaźniki awarii, które chciałem przetestować, zmieniając 10 procent na 25, a następnie 50 i dodając te tabele również do model :

USE model;
GO
 
DECLARE @pct INT = 10, @cap INT = 20000;
-- change this ----^^ to 25 and 50
 
DECLARE @good INT = @cap - (@cap*(@pct/100.0));
 
SELECT Name, rn = ROW_NUMBER() OVER (ORDER BY NEWID()) 
INTO dbo.Source10Percent FROM 
-- change this ^^ to 25 and 50
(
  SELECT Name FROM 
  (
    SELECT TOP (@good) Name FROM dbo.GoodNamesSource ORDER BY NEWID()
  ) AS g
  UNION ALL
  SELECT Name FROM 
  (
    SELECT TOP (@cap-@good) Name FROM dbo.NaughtyUserNames ORDER BY NEWID()
  ) AS b
) AS x;
 
CREATE UNIQUE CLUSTERED INDEX x ON dbo.Source10Percent(rn);
-- and here as well -------------------------^^

Każda tabela ma 20 000 wierszy, z inną mieszanką nazw, które zakończą się powodzeniem i niepowodzeniem, a kolumna numeru wiersza ułatwia podział danych na różne wielkości partii dla różnych testów, ale z powtarzalnymi wskaźnikami niepowodzeń dla wszystkich testów.

Oczywiście potrzebujemy miejsca na uchwycenie wyników. Zdecydowałem się użyć do tego oddzielnej bazy danych, uruchamiając każdy test wiele razy, po prostu przechwytując czas trwania.

CREATE DATABASE ControlDB;
GO
 
USE ControlDB;
GO
 
CREATE TABLE dbo.Tests
(
  TestID        INT, 
  DiskLayout    VARCHAR(15),
  RecoveryModel VARCHAR(6),
  TriggerType   VARCHAR(14),
  [snapshot]    VARCHAR(3),
  FailureRate   INT,
  [sql]         NVARCHAR(MAX)
);
 
CREATE TABLE dbo.TestResults
(
  TestID INT,
  BatchDescription VARCHAR(15),
  Duration INT
);

Wypełniłem dbo.Tests tabeli z następującym skryptem, abym mógł wykonać różne części, aby ustawić cztery bazy danych tak, aby odpowiadały bieżącym parametrom testu. Zwróć uwagę, że D:\ to dysk SSD, a G:\ to dysk o prędkości 7200 obr./min:

TRUNCATE TABLE dbo.Tests;
TRUNCATE TABLE dbo.TestResults;
 
;WITH d AS 
(
  SELECT DiskLayout FROM (VALUES
    ('DataSSD_LogHDD'),
    ('DataSSD_LogSSD'),
    ('DataHDD_LogHDD'),
    ('DataHDD_LogSSD')) AS d(DiskLayout)
),
t AS 
(
  SELECT TriggerType FROM (VALUES
  ('After_Delete'),
  ('After_Rollback'),
  ('Instead')) AS t(TriggerType)
),
m AS 
(
  SELECT RecoveryModel = 'FULL' 
      UNION ALL SELECT 'SIMPLE'
),
s AS 
(
  SELECT IsSnapshot = 0 
      UNION ALL SELECT 1
),
p AS 
(
  SELECT FailureRate = 10 
      UNION ALL SELECT 25 
	  UNION ALL SELECT 50
)
INSERT ControlDB.dbo.Tests
(
  TestID, 
  DiskLayout, 
  RecoveryModel, 
  TriggerType, 
  IsSnapshot, 
  FailureRate, 
  Command
)
SELECT 
  TestID = ROW_NUMBER() OVER 
  (
    ORDER BY d.DiskLayout, t.TriggerType, m.RecoveryModel, s.IsSnapshot, p.FailureRate
  ),
  d.DiskLayout, 
  m.RecoveryModel, 
  t.TriggerType, 
  s.IsSnapshot, 
  p.FailureRate, 
  [sql]= N'SET NOCOUNT ON;
 
CREATE DATABASE ' + QUOTENAME(d.DiskLayout) 
 + N' ON (name = N''data'', filename = N''' + CASE d.DiskLayout 
WHEN 'DataSSD_LogHDD' THEN N'D:\data\data1.mdf'') 
  LOG ON (name = N''log'', filename = N''G:\log\data1.ldf'');'
WHEN 'DataSSD_LogSSD' THEN N'D:\data\data2.mdf'') 
  LOG ON (name = N''log'', filename = N''D:\log\data2.ldf'');'
WHEN 'DataHDD_LogHDD' THEN N'G:\data\data3.mdf'') 
  LOG ON (name = N''log'', filename = N''G:\log\data3.ldf'');'
WHEN 'DataHDD_LogSSD' THEN N'G:\data\data4.mdf'') 
  LOG ON (name = N''log'', filename = N''D:\log\data4.ldf'');' END
+ '
EXEC sp_executesql N''ALTER DATABASE ' + QUOTENAME(d.DiskLayout) 
  + ' SET RECOVERY ' + m.RecoveryModel + ';'';'
+ CASE WHEN s.IsSnapshot = 1 THEN 
'
EXEC sp_executesql N''ALTER DATABASE ' + QUOTENAME(d.DiskLayout) 
  + ' SET ALLOW_SNAPSHOT_ISOLATION ON;'';
EXEC sp_executesql N''ALTER DATABASE ' + QUOTENAME(d.DiskLayout) 
  + ' SET READ_COMMITTED_SNAPSHOT ON;'';' 
ELSE '' END
+ '
 
DECLARE @d DATETIME2(7), @i INT, @LoopID INT, @loops INT, @perloop INT;
 
DECLARE c CURSOR LOCAL FAST_FORWARD FOR
  SELECT LoopID, loops, perloop FROM dbo.Loops; 
 
OPEN c;
 
FETCH c INTO @LoopID, @loops, @perloop;
 
WHILE @@FETCH_STATUS <> -1
BEGIN
  EXEC sp_executesql N''TRUNCATE TABLE ' 
    + QUOTENAME(d.DiskLayout) + '.dbo.UserNames_' + t.TriggerType + ';'';
 
  SELECT @d = SYSDATETIME(), @i = 1;
 
  WHILE @i <= @loops
  BEGIN
    BEGIN TRY
      INSERT ' + QUOTENAME(d.DiskLayout) + '.dbo.UserNames_' + t.TriggerType + '(Name)
        SELECT Name FROM ' + QUOTENAME(d.DiskLayout) + '.dbo.Source' + RTRIM(p.FailureRate) + 'Percent
	    WHERE rn > (@i-1)*@perloop AND rn <= @i*@perloop;
    END TRY
    BEGIN CATCH
      SET @TestID = @TestID;
    END CATCH
 
    SET @i += 1;
  END
 
  INSERT ControlDB.dbo.TestResults(TestID, LoopID, Duration)
    SELECT @TestID, @LoopID, DATEDIFF(MILLISECOND, @d, SYSDATETIME());
 
  FETCH c INTO @LoopID, @loops, @perloop;
END
 
CLOSE c;
DEALLOCATE c;
 
DROP DATABASE ' + QUOTENAME(d.DiskLayout) + ';'
FROM d, t, m, s, p;  -- implicit CROSS JOIN! Do as I say, not as I do! :-)

Następnie można było łatwo przeprowadzić wszystkie testy wielokrotnie:

USE ControlDB;
GO
 
SET NOCOUNT ON;
 
DECLARE @TestID INT, @Command NVARCHAR(MAX), @msg VARCHAR(32);
 
DECLARE d CURSOR LOCAL FAST_FORWARD FOR 
  SELECT TestID, Command
    FROM ControlDB.dbo.Tests ORDER BY TestID;
 
OPEN d;
 
FETCH d INTO @TestID, @Command;
 
WHILE @@FETCH_STATUS <> -1
BEGIN
  SET @msg = 'Starting ' + RTRIM(@TestID);
  RAISERROR(@msg, 0, 1) WITH NOWAIT;
 
  EXEC sp_executesql @Command, N'@TestID INT', @TestID;
 
  SET @msg = 'Finished ' + RTRIM(@TestID);
  RAISERROR(@msg, 0, 1) WITH NOWAIT;
 
  FETCH d INTO @TestID, @Command;
END
 
CLOSE d;
DEALLOCATE d;
 
GO 10

W moim systemie zajęło to prawie 6 godzin, więc przygotuj się na nieprzerwane działanie. Upewnij się również, że nie masz żadnych aktywnych połączeń ani otwartych okien zapytań względem model bazy danych, w przeciwnym razie możesz otrzymać ten błąd, gdy skrypt próbuje utworzyć bazę danych:

Msg 1807, poziom 16, stan 3
Nie można uzyskać blokady na wyłączność „modelu” bazy danych. Ponów operację później.

Wyniki

Istnieje wiele punktów danych, którym należy się przyjrzeć (a wszystkie zapytania używane do uzyskania danych znajdują się w Dodatku). Pamiętaj, że każdy podany tutaj średni czas trwania to ponad 10 testów i wstawia łącznie 100 000 wierszy do tabeli docelowej.

Wykres 1 – Ogólne agregaty

Pierwszy wykres pokazuje ogólne agregaty (średni czas trwania) dla różnych zmiennych w izolacji (czyli *wszystkie* testy używające wyzwalacza AFTER, który usuwa, *wszystkie* testy używające wyzwalacza AFTER, który cofa się itd.).


Średni czas trwania w milisekundach dla każdej izolowanej zmiennej

Kilka rzeczy wyskakuje nam od razu:

  • Tutaj wyzwalacz INSTEAD OF jest dwa razy szybszy niż oba wyzwalacze AFTER.
  • Dziennik transakcji na dysku SSD zrobił pewną różnicę. Znacznie mniej lokalizacji pliku danych.
  • Partia 20 000 pojedynczych wkładek była 7-8 razy wolniejsza niż jakakolwiek inna dystrybucja partii.
  • Wstawianie pojedynczej partii z 20 000 wierszy było wolniejsze niż którakolwiek z dystrybucji innych niż pojedyncze.
  • Wskaźnik awarii, izolacja migawek i model odzyskiwania miały niewielki, jeśli w ogóle, wpływ na wydajność.

Wykres 2 – Najlepsze 10 ogółem

Ten wykres pokazuje 10 najszybszych wyników, gdy uwzględni się każdą zmienną. Są to wszystkie wyzwalacze INSTEAD OF, w których największy procent wierszy kończy się niepowodzeniem (50%). Co zaskakujące, najszybszy (choć nie za dużo) miał zarówno dane, jak i logowanie na tym samym dysku twardym (nie SSD). Istnieje tutaj mieszanka układów dysków i modeli odzyskiwania, ale wszystkie 10 miały włączoną izolację migawek, a wszystkie 7 najlepszych wyników obejmowało rozmiar partii 10 x 2000 wierszy.


10 najlepszych czasów trwania w milisekundach, biorąc pod uwagę każdą zmienną

Najszybszy wyzwalacz AFTER – wariant ROLLBACK z 10% współczynnikiem awaryjności w wielkości partii rzędu 100 x 200 – znalazł się na pozycji #144 (806 ms).

Wykres 3 – Najgorsze 10 ogólnie

Ten wykres pokazuje najwolniejsze 10 wyników, gdy weźmie się pod uwagę każdą zmienną; wszystkie są wariantami PO, wszystkie obejmują 20 000 pojedynczych wkładek i wszystkie mają dane i logują się na tym samym wolnym dysku twardym.


10 najgorszych czasów trwania w milisekundach, biorąc pod uwagę każdą zmienną

Najwolniejszy test INSTEAD OF znajdował się na pozycji 97, przy 5680 ms – 20 000 testach z pojedynczą wkładką, w którym 10% zakończyło się niepowodzeniem. Interesujące jest również to, że ani jeden wyzwalacz AFTER przy użyciu wielkości partii 20 000 pojedynczych wkładek nie wypadł lepiej – w rzeczywistości 96. najgorszy wynik to test PO (usuwanie), który pojawił się po 10 219 ms – prawie dwukrotność kolejnego najwolniejszego wyniku.

Wykres 4 – Typ dysku dziennika, wstawki singletonowe

Powyższe wykresy dają nam z grubsza wyobrażenie o największych problemach, ale są one albo zbyt powiększone, albo niewystarczająco powiększone. Ten wykres filtruje dane oparte na rzeczywistości:w większości przypadków tego typu operacja będzie wstawką singletonową. Pomyślałem, że podzielę to według wskaźnika awaryjności i typu dysku, na którym znajduje się dziennik, ale spójrz tylko na wiersze, w których partia składa się z 20 000 pojedynczych wstawek.


Czas trwania w milisekundach, pogrupowany według wskaźnika awarii i lokalizacji dziennika, na 20 000 pojedynczych wkładek

Widzimy tutaj, że wszystkie wyzwalacze AFTER są średnio w zakresie 10-11 sekund (w zależności od lokalizacji dziennika), podczas gdy wszystkie wyzwalacze INSTEAD OF są znacznie poniżej znaku 6 sekund.

Wniosek

Jak dotąd wydaje mi się jasne, że wyzwalacz INSTEAD OF jest zwycięzcą w większości przypadków – w niektórych przypadkach bardziej niż w innych (na przykład, gdy wskaźnik niepowodzeń rośnie). Inne czynniki, takie jak model odzyskiwania, wydają się mieć znacznie mniejszy wpływ na ogólną wydajność.

Jeśli masz inne pomysły na rozbicie danych lub chcesz otrzymać kopię danych do samodzielnego krojenia i kostkowania, daj mi znać. Jeśli potrzebujesz pomocy w skonfigurowaniu tego środowiska, aby móc przeprowadzać własne testy, mogę również w tym pomóc.

Chociaż ten test pokazuje, że wyzwalacze INSTEAD OF są zdecydowanie warte rozważenia, nie jest to cała historia. Dosłownie połączyłem te wyzwalacze, używając logiki, która moim zdaniem była najbardziej sensowna dla każdego scenariusza, ale kod wyzwalacza – jak każde polecenie T-SQL – można dostroić w celu uzyskania optymalnych planów. W kolejnym poście przyjrzę się potencjalnej optymalizacji, która może sprawić, że element AFTER będzie bardziej konkurencyjny.

Załącznik

Zapytania użyte w sekcji Wyniki:

Wykres 1 – Ogólne agregaty

SELECT RTRIM(l.loops) + ' x ' + RTRIM(l.perloop), AVG(r.Duration*1.0)
  FROM dbo.TestResults AS r
  INNER JOIN dbo.Loops AS l
  ON r.LoopID = l.LoopID
  GROUP BY RTRIM(l.loops) + ' x ' + RTRIM(l.perloop);
 
SELECT t.IsSnapshot, AVG(Duration*1.0)
  FROM dbo.TestResults AS tr
  INNER JOIN dbo.Tests AS t
  ON tr.TestID = t.TestID 
  GROUP BY t.IsSnapshot;
 
SELECT t.RecoveryModel, AVG(Duration*1.0)
  FROM dbo.TestResults AS tr
  INNER JOIN dbo.Tests AS t
  ON tr.TestID = t.TestID 
  GROUP BY t.RecoveryModel;
 
SELECT t.DiskLayout, AVG(Duration*1.0)
  FROM dbo.TestResults AS tr
  INNER JOIN dbo.Tests AS t
  ON tr.TestID = t.TestID 
  GROUP BY t.DiskLayout;
 
SELECT t.TriggerType, AVG(Duration*1.0)
  FROM dbo.TestResults AS tr
  INNER JOIN dbo.Tests AS t
  ON tr.TestID = t.TestID 
  GROUP BY t.TriggerType;
 
SELECT t.FailureRate, AVG(Duration*1.0)
  FROM dbo.TestResults AS tr
  INNER JOIN dbo.Tests AS t
  ON tr.TestID = t.TestID 
  GROUP BY t.FailureRate;

Wykres 2 i 3 – Najlepsze i najgorsze 10

;WITH src AS 
(
    SELECT DiskLayout, RecoveryModel, TriggerType, FailureRate, IsSnapshot,
      Batch = RTRIM(l.loops) + ' x ' + RTRIM(l.perloop),
      Duration = AVG(Duration*1.0)
    FROM dbo.Tests AS t
    INNER JOIN dbo.TestResults AS tr
    ON tr.TestID = t.TestID 
    INNER JOIN dbo.Loops AS l
    ON tr.LoopID = l.LoopID
    GROUP BY DiskLayout, RecoveryModel, TriggerType, FailureRate, IsSnapshot,
      RTRIM(l.loops) + ' x ' + RTRIM(l.perloop)
),
agg AS
(
    SELECT label = REPLACE(REPLACE(DiskLayout,'Data',''),'_Log','/')
      + ', ' + RecoveryModel + ' recovery, ' + TriggerType
  	+ ', ' + RTRIM(FailureRate) + '% fail'
	+ ', Snapshot = ' + CASE IsSnapshot WHEN 1 THEN 'ON' ELSE 'OFF' END
  	+ ', ' + Batch + ' (ops x rows)',
      best10  = ROW_NUMBER() OVER (ORDER BY Duration), 
      worst10 = ROW_NUMBER() OVER (ORDER BY Duration DESC),
      Duration
    FROM src
)
SELECT grp, label, Duration FROM
(
  SELECT TOP (20) grp = 'best', label = RIGHT('0' + RTRIM(best10),2) + '. ' + label, Duration
    FROM agg WHERE best10 <= 10
    ORDER BY best10 DESC
  UNION ALL
  SELECT TOP (20) grp = 'worst', label = RIGHT('0' + RTRIM(worst10),2) + '. ' + label, Duration
    FROM agg WHERE worst10 <= 10
    ORDER BY worst10 DESC
  ) AS b
  ORDER BY grp;

Wykres 4 – Typ dysku dziennika, wstawki singletonowe

;WITH x AS
(
    SELECT 
      TriggerType,FailureRate,
      LogLocation = RIGHT(DiskLayout,3), 
      Duration = AVG(Duration*1.0)
    FROM dbo.TestResults AS tr
    INNER JOIN dbo.Tests AS t
    ON tr.TestID = t.TestID 
    INNER JOIN dbo.Loops AS l
    ON l.LoopID = tr.LoopID
    WHERE l.loops = 20000
    GROUP BY RIGHT(DiskLayout,3), FailureRate, TriggerType
)
SELECT TriggerType, FailureRate, 
  HDDDuration = MAX(CASE WHEN LogLocation = 'HDD' THEN Duration END),
  SSDDuration = MAX(CASE WHEN LogLocation = 'SSD' THEN Duration END)
FROM x 
GROUP BY TriggerType, FailureRate
ORDER BY TriggerType, FailureRate;

  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Identyfikacja struktury zestawienia komponentów (BOM) w bazach danych

  2. Używanie flagi śledzenia 3226 do pomijania rejestrowania kopii zapasowej dziennika

  3. Pierwsze kroki z Cloud Firestore na iOS

  4. Kilka szybkich rzeczy na temat opinii PASS

  5. Wielkość próbki i czas trwania AKTUALIZACJI STATYSTYK:czy to ma znaczenie?