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

T-SQL Wtorek #106 :ZAMIAST wyzwalaczy

We wtorek w T-SQL w tym miesiącu Steve Jones (@way0utwest) poprosił nas o omówienie naszych najlepszych lub najgorszych doświadczeń z wyzwalaczami. Chociaż prawdą jest, że wyzwalacze są często niemile widziane, a nawet obawiają się, mają kilka ważnych przypadków użycia, w tym:

  • Audyt (przed dodatkiem SP1 2016, kiedy ta funkcja stała się bezpłatna we wszystkich wydaniach)
  • Egzekwowanie reguł biznesowych i integralności danych, gdy nie można ich łatwo zaimplementować w ograniczeniach, a nie chcesz, aby były zależne od kodu aplikacji lub samych zapytań DML
  • Utrzymywanie historycznych wersji danych (przed przechwytywaniem danych zmian, śledzeniem zmian i tabelami czasowymi)
  • Kolejkowanie alertów lub przetwarzanie asynchroniczne w odpowiedzi na określoną zmianę
  • Zezwalanie na modyfikacje widoków (poprzez wyzwalacze ZAMIAST)

To nie jest wyczerpująca lista, tylko krótkie podsumowanie kilku scenariuszy, których doświadczyłem, w których wyzwalacze były wtedy właściwą odpowiedzią.

Kiedy wyzwalacze są konieczne, zawsze lubię badać użycie wyzwalaczy zamiast wyzwalaczy PO. Tak, są one trochę bardziej wstępne*, ale mają kilka całkiem ważnych zalet. Przynajmniej teoretycznie perspektywa zapobiegania działaniu (i jego konsekwencjom w dzienniku) wydaje się o wiele bardziej skuteczna niż pozwolenie na to, aby wszystko się wydarzyło, a następnie cofnięcie go.

*

Mówię to, ponieważ musisz ponownie zakodować instrukcję DML w wyzwalaczu; dlatego nie nazywa się ich PRZED wyzwalaczami. Rozróżnienie jest tutaj ważne, ponieważ niektóre systemy implementują prawdziwe wyzwalacze PRZED, które po prostu uruchamiają się jako pierwsze. W SQL Server wyzwalacz INSTEAD OF skutecznie anuluje instrukcję, która spowodowała jego uruchomienie.

Załóżmy, że mamy prostą tabelę do przechowywania nazw kont. W tym przykładzie utworzymy dwie tabele, dzięki czemu będziemy mogli porównać dwa różne wyzwalacze i ich wpływ na czas trwania zapytania i wykorzystanie dziennika. Koncepcja polega na tym, że mamy regułę biznesową:nazwa konta nie jest obecna w innej tabeli, która reprezentuje „złe” nazwy, a wyzwalacz jest używany do wymuszenia tej reguły. Oto baza danych:

USE [master];
GO
CREATE DATABASE [tr] ON (name = N'tr_dat', filename = N'C:\temp\tr.mdf', size = 4096MB)
                 LOG ON (name = N'tr_log', filename = N'C:\temp\tr.ldf', size = 2048MB);
GO
ALTER DATABASE [tr] SET RECOVERY FULL;
GO

A stoły:

USE [tr];
GO
 
CREATE TABLE dbo.Accounts_After
(
  AccountID int PRIMARY KEY,
  name      sysname UNIQUE,
  filler    char(255) NOT NULL DEFAULT '' 
);
 
CREATE TABLE dbo.Accounts_Instead
(
  AccountID int PRIMARY KEY,
  name      sysname UNIQUE,
  filler    char(255) NOT NULL DEFAULT ''
);
 
CREATE TABLE dbo.InvalidNames
(
  name sysname PRIMARY KEY
);
 
INSERT dbo.InvalidNames(name) VALUES (N'poop'),(N'hitler'),(N'boobies'),(N'cocaine');

I wreszcie wyzwalacze. Dla uproszczenia mamy do czynienia tylko z wstawkami, i zarówno w przypadku po, jak i zamiast, po prostu przerwiemy całą partię, jeśli jakakolwiek nazwa narusza naszą regułę:

CREATE TRIGGER dbo.tr_Accounts_After
ON dbo.Accounts_After
AFTER INSERT
AS
BEGIN
  IF EXISTS
  (
    SELECT 1 FROM inserted AS i
      INNER JOIN dbo.InvalidNames AS n
      ON i.name = n.name
  )
  BEGIN
    RAISERROR(N'Tsk tsk.', 11, 1);
    ROLLBACK TRANSACTION;
    RETURN;
  END
END
GO
 
CREATE TRIGGER dbo.tr_Accounts_Instead
ON dbo.Accounts_After
INSTEAD OF INSERT
AS
BEGIN
  IF EXISTS
  (
    SELECT 1 FROM inserted AS i
      INNER JOIN dbo.InvalidNames AS n
      ON i.name = n.name
  )
  BEGIN
    RAISERROR(N'Tsk tsk.', 11, 1);
    RETURN;
  END
  ELSE
  BEGIN
    INSERT dbo.Accounts_Instead(AccountID, name, filler)
      SELECT AccountID, name, filler FROM inserted;
  END
END
GO

Teraz, aby przetestować wydajność, spróbujemy po prostu wstawić 100 000 nazw do każdej tabeli, z przewidywalnym wskaźnikiem niepowodzeń wynoszącym 10%. Innymi słowy, 90 000 nazw jest w porządku, pozostałe 10 000 nie przechodzi testu i powoduje, że wyzwalacz albo cofa się, albo nie jest wstawiany, w zależności od partii.

Najpierw musimy zrobić trochę porządków przed każdą partią:

TRUNCATE TABLE dbo.Accounts_Instead;
TRUNCATE TABLE dbo.Accounts_After;
GO
CHECKPOINT;
CHECKPOINT;
BACKUP LOG triggers TO DISK = N'C:\temp\tr.trn' WITH INIT, COMPRESSION;
GO

Zanim zaczniemy mięso z każdej partii, policzymy wiersze w dzienniku transakcji i zmierzymy rozmiar oraz wolne miejsce. Następnie przejdziemy przez kursor, aby przetworzyć 100 000 wierszy w losowej kolejności, próbując wstawić każdą nazwę do odpowiedniej tabeli. Kiedy skończymy, ponownie zmierzymy liczbę wierszy i rozmiar dziennika oraz sprawdzimy czas trwania.

SET NOCOUNT ON;
 
DECLARE @batch varchar(10)  = 'After', -- or After
        @d     datetime2(7) = SYSUTCDATETIME(),
        @n     nvarchar(129),
        @i     int,
        @err   nvarchar(512);
 
-- measure before and again when we're done:
SELECT COUNT(*) FROM sys.fn_dblog(NULL, NULL);
 
SELECT CurrentSizeMB = size/128.0,  
       FreeSpaceMB   = (size-CONVERT(int, FILEPROPERTY(name,N'SpaceUsed')))/128.0
FROM sys.database_files
WHERE name = N'tr_log';
 
DECLARE c CURSOR LOCAL FAST_FORWARD
FOR 
  SELECT name, i = ROW_NUMBER() OVER (ORDER BY NEWID())
  FROM
  (
    SELECT DISTINCT TOP (90000) LEFT(o.name,64) + '/' + LEFT(c.name,63)
      FROM sys.all_objects AS o
      CROSS JOIN sys.all_columns AS c
    UNION ALL 
    SELECT TOP (10000) N'boobies' FROM sys.all_columns
  ) AS x (name)
  ORDER BY i;
 
OPEN c;
 
FETCH NEXT FROM c INTO @n, @i;
 
WHILE @@FETCH_STATUS = 0
BEGIN
  BEGIN TRY
    IF @batch = 'After'
      INSERT dbo.Accounts_After(AccountID,name) VALUES(@i,@n);
    IF @batch = 'Instead'
      INSERT dbo.Accounts_Instead(AccountID,name) VALUES(@i,@n);
  END TRY
  BEGIN CATCH 
    SET @err = ERROR_MESSAGE();
  END CATCH
  FETCH NEXT FROM c INTO @n, @i;
END
 
-- measure again when we're done:
SELECT COUNT(*) FROM sys.fn_dblog(NULL, NULL);
 
SELECT duration = DATEDIFF(MILLISECOND, @d, SYSUTCDATETIME()),
  CurrentSizeMB = size/128.0,  
  FreeSpaceMB   = (size-CAST(FILEPROPERTY(name,N'SpaceUsed') AS int))/128.0
FROM sys.database_files
WHERE name = N'tr_log';
 
CLOSE c; DEALLOCATE c;

Wyniki (uśrednione z 5 przebiegów każdej partii):

PO vs. ZAMIAST:Wyniki

W moich testach użycie dziennika było prawie identyczne, z ponad 10% większą liczbą wierszy dziennika generowanych przez wyzwalacz INSTEAD OF. Pod koniec każdej partii kopałem trochę:

SELECT [Operation], COUNT(*)
  FROM sys.fn_dblog(NULL, NULL) 
  GROUP BY [Operation]
  ORDER BY [Operation];

A oto typowy wynik (podkreśliłem główne delty):

Dystrybucja wierszy dziennika

Zagłębię się w to głębiej innym razem.

Ale kiedy się do tego zabrać…

…najważniejszą metryką prawie zawsze będzie czas trwania , aw moim przypadku spust INSTEAD OF działał co najmniej 5 sekund szybciej w każdym pojedynczym teście head-to-head. Jeśli to wszystko brzmi znajomo, tak, rozmawiałem o tym wcześniej, ale wtedy nie zauważyłem tych samych objawów w wierszach dziennika.

Pamiętaj, że może to nie być dokładny schemat lub obciążenie, możesz mieć bardzo inny sprzęt, współbieżność może być wyższa, a wskaźnik niepowodzeń może być znacznie wyższy (lub niższy). Moje testy przeprowadzono na izolowanej maszynie z dużą ilością pamięci i bardzo szybkimi dyskami SSD PCIe. Jeśli dziennik znajduje się na wolniejszym dysku, różnice w wykorzystaniu dziennika mogą przeważyć nad innymi metrykami i znacznie zmienić czas trwania. Wszystkie te czynniki (i więcej!) mogą wpłynąć na Twoje wyniki, więc powinieneś testować w swoim środowisku.

Chodzi jednak o to, że wyzwalacze zamiast wyzwalaczy mogą być lepiej dopasowane. Teraz, gdybyśmy tylko mogli uzyskać wyzwalacze ZAMIAST DDL…


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Raportowanie użycia opcji/pakietów bazy danych

  2. Porównanie metod dzielenia/konkatenacji łańcuchów

  3. 7 bezpłatnych narzędzi do tworzenia diagramów baz danych dla zajętych użytkowników danych

  4. Nieoczekiwany efekt uboczny dodania filtrowanego indeksu

  5. Co to jest indeks w SQL?