В SQL Server тригерите са обекти на база данни, които ще се изпълняват всеки път, когато се случи задействащо събитие в базата данни или сървъра. Тригерите играят ключова роля в постигането на бизнес изисквания като известяване на целевите хора въз основа на постигнато състояние, започване на работа или други операции. В предишната статия за DML тригери говорихме за тригери, типове тригери и различни опции за задействане, налични за DML тригери. В тази статия ще разгледаме тригерите за SQL DDL и LOGON.
DDL тригери
DDL тригерите могат да се задействат за различни събития с обхват на сървър или база данни, включително DDL и DCL команди. DDL означава език за дефиниране на данни, който се използва за СЪЗДАВАНЕ, ПРОМЕНЯНЕ, ОТПУСКАНЕ на всякакви обекти, а DCL означава изрази на езика за управление на данни като команди GRANT, DENY и REVOKE. По-долу са характеристиките на SQL DDL тригери.
- DDL тригери могат да бъдат създадени на ниво база данни или ниво на екземпляр на сървъра, обхващащи голямо разнообразие от DDL операции или подобни на DDL операции, напр. DCL команди.
- DDL тригерите могат да бъдат извиквани или задействани само като тип задействане FOR или AFTER. SQL Server не поддържа ВМЕСТО DDL Trigger и можем да видим как да предотвратим някои DDL операции чрез DDL тригери.
- SQL Server има вградени функции като EVENTDATA() и IS_MEMBER() за използване в рамките на DDL тригери за получаване на повече информация, свързана със събитията Trigger.
- Функцията EVENTDATA() връща пълни подробности за събития в обхвата на базата данни или сървъра в XML формат в обхвата на тригера DDL с обхват на базата данни или сървъра или тригерите за влизане. Функцията EVENTDATA() връща пълните подробности за събитието за сесията, която изпълнява дейностите DDL или влизане. EVENTDATA() връща подробностите по-долу
- EventType – Тип на събитието, което задейства тригера DDL, наличен в таблицата sys.trigger_event_types.
- PostTime – Час, когато събитието е било задействано или публикувано.
- SPID – ID на сесията на събитието.
- ServerName – име на екземпляр на SQL Server, в който е задействано събитието.
- Име за вход – Име за вход в SQL Server, което е извършило събитието.
- Потребителско име – потребителско име на входа, което ще бъде dbo по подразбиране.
- Име на база данни – Име на база данни, под което е задействан DDL тригерът.
- SchemaName – Име на схемата на обекта, който е бил засегнат.
- ObjectName – Име на обект, който е бил засегнат.
- ObjectType – Тип обект на SQL Server като таблица, изглед, съхранена процедура.
- TSQLCommand – T-SQL скрипт, който е изпълнен от потребител, който е извикал DDL тригера.
- SetOptions – SET опции, използвани от потребител или клиент като SSMS, докато TSQLCommand е изпълнен.
- CommandText – Действителни DDL или DCL изрази с DDL събитието, посочено в таблицата sys.trigger_event_types.
- Функцията IS_MEMBER() връща дали текущият потребител е член на групата на Windows или ролята на база данни на SQL Server или не.
- Функцията EVENTDATA() връща пълни подробности за събития в обхвата на базата данни или сървъра в XML формат в обхвата на тригера DDL с обхват на базата данни или сървъра или тригерите за влизане. Функцията EVENTDATA() връща пълните подробности за събитието за сесията, която изпълнява дейностите DDL или влизане. EVENTDATA() връща подробностите по-долу
- System DMV sys.triggers съхранява списъка на всички тригери в обхвата на базата данни. Можем да използваме заявката по-долу, за да извлечем подробности за всички DDL тригери с обхват на базата данни.
SELECT *
FROM sys.triggers
WHERE type = 'TR';
- Системният DMV sys.server_triggers съхранява списъка на всички тригери в обхвата на сървъра и можем да използваме заявката по-долу, за да извлечем подробности за всички DDL тригери в обхвата на сървъра.
SELECT *
FROM sys.server_triggers;
- Дефинициите на задействане на DDL могат да се видят, ако тригерът не е криптиран, като се използва някоя от опциите по-долу от sys.sql_modules или чрез функцията OBJECT_DEFINITION() или чрез съхранена процедура sp_helptext.
SELECT OBJECT_SCHEMA_NAME(object_id, db_id()) Schema_name, OBJECT_NAME(object_id) Trigger_Name, definition
FROM sys.sql_modules
WHERE object_id = OBJECT_ID(<trigger_name>);
SELECT OBJECT_DEFINITION (OBJECT_ID(<trigger_name>)) AS ObjectDefinition;
EXEC sp_helptext '<trigger_name>';
- Всички възможни DDL събития са налични в таблицата sys.trigger_event_types и могат да бъдат прегледани с помощта на заявката по-долу.
SELECT *
FROM sys.trigger_event_types;
Синтаксисът на DDL Trigger е:
CREATE TRIGGER <trigger_name>
ON < ALL SERVER | DATABASE >
[ WITH <DDL_trigger_option> [ ,...n ] ]
{ FOR | AFTER } <event_type>
AS { sql_statement | EXTERNAL NAME <method specifier> }
Създаване на DDL тригер с обхват на база данни
Нека създадем тригер с обхват на базата данни, за да проследим всички създаване на таблици и да влезем в таблица за регистриране на име Track_DDL_Changes, като използваме скрипта по-долу.
CREATE TABLE Track_DDL_Changes (EventData xml, PostDtm datetime)
GO
CREATE TRIGGER TR_D_CREATETABLE
ON DATABASE
FOR CREATE_TABLE
AS
BEGIN
INSERT INTO Track_DDL_Changes
SELECT EVENTDATA(),GETDATE()
END
GO
Нека създадем нова таблица с име trigger_test и да проверим дали събитието CREATE TABLE е одитирано или не, като използваме скрипта по-долу.
CREATE TABLE Trigger_Test ( a int, b datetime);
Избирането на данните от таблицата Track_DDL_Changes показва, че горното събитие CREATE_TABLE е заснето успешно, както е показано по-долу:
Щракването върху стойността EventData ще отвори стойността XML EVENTDATA() в нов прозорец, както е показано по-долу.
Ние можем да проверим пълните подробности за задействащото събитие чрез функцията EVENTDATA() и следователно функцията EVENTDATA() би изиграла значителна роля за всякакви DDL или LOGON тригери.
Можем допълнително да подобрим нашия DDL Trigger с помощта на функцията EVENTDATA() и XML синтактичен анализ и да попречим на всеки да създаде каквато и да е таблица в тестовата база данни с помощта на скрипта, даден по-долу:
CREATE TRIGGER TR_D_PREVENT_CREATETABLE
ON DATABASE
FOR CREATE_TABLE
AS
BEGIN
SELECT EVENTDATA().value
('(/EVENT_INSTANCE/TSQLCommand/CommandText)[1]','nvarchar(max)')
RAISERROR ('Creation of New tables restricted in this database, Kindly contact DBA.', 16, 1)
ROLLBACK
END
GO
Създаването на тригер с обхват на базата данни завърши успешно и нека проверим, като създадем друга таблица с помощта на скрипта по-долу.
CREATE TABLE Trigger_Test1 (a int, b datetime);
Тригерът ни попречи да създадем нови таблици в тази база данни и остави смислено съобщение и на потребителите. По подобен начин можем да обработваме всякакви други събития с обхват на DDL или сървър, за да отговарят на изискванията.
За да премахнем DDL тригера с обхват на базата данни, трябва да използваме следния синтаксис:
DROP TRIGGER <trigger_name> ON DATABASE;
И за да махнем спусъка, който създадохме току-що, скриптът ще бъде
DROP TRIGGER TR_D_PREVENT_CREATETABLE ON DATABASE;
За да видите DDL тригерите с обхват на базата данни в SSMS, разгънете Тестовата база данни -> Програмируемост -> Тригери на базата данни, както е показано по-долу.
Подобно на SQL DML тригерите, DDL тригерите могат да бъдат премахнати, деактивирани или активирани, като просто щракнете с десния бутон върху името на тригера, както е показано по-долу.
Чрез T-SQL можем да пуснем или деактивираме или активираме DDL тригера с обхват на базата данни, използвайки следния синтаксис:
-- DROP Database scoped DDL Trigger
DROP TRIGGER <trigger_name> ON DATABASE;
-- Enable Database scoped DDL Trigger
ENABLE TRIGGER <trigger_name> ON DATABASE;
-- Disable Database scoped DDL Trigger
DISABLE TRIGGER <trigger_name> ON DATABASE;
За да деактивираме създадения от нас тригер, може да се наложи да използваме скрипта по-долу.
-- DROP Database scoped DDL Trigger
DROP TRIGGER TR_D_PREVENT_CREATETABLE ON DATABASE;
-- Enable Database scoped DDL Trigger
ENABLE TRIGGER TR_D_PREVENT_CREATETABLE ON DATABASE;
-- Disable Database scoped DDL Trigger
DISABLE TRIGGER TR_D_PREVENT_CREATETABLE ON DATABASE;
Създаване на DDL тригер с обхват на сървъра
DDL тригерът с обхват на сървъра следва същия синтаксис, подобен на DDL тригера за обхват на базата данни, с изключение на това, че събитията ще се базират на обхвата на сървъра.
Нека се опитаме да създадем DDL тригер с обхват на сървъра, за да попречим на всеки потребител да създаде нова база данни на този сървърен екземпляр с помощта на скрипта по-долу.
CREATE TRIGGER TR_S_PREVENT_CREATEDATABASE
ON ALL SERVER
FOR CREATE_DATABASE
AS
BEGIN
SELECT EVENTDATA().value
('(/EVENT_INSTANCE/TSQLCommand/CommandText)[1]','nvarchar(max)')
RAISERROR ('Creation of New Databases restricted in this Instance, Kindly contact DBA.', 16, 1)
ROLLBACK
END
GO
Когато се опитваме да създадем нова база данни с помощта на командата по-долу, ще получим грешка, както е показано по-долу.
CREATE DATABASE DATABASE_TEST;
В SSMS DDL тригери с обхват на сървъра под Тригери в секцията сървърни обекти, както е показано по-долу.
Можем да премахнем, деактивираме или активираме DDL тригера с обхват на сървъра, като просто щракнем с десния бутон върху DDL тригера с обхват на сървъра, както е показано по-долу.
Чрез T-SQL можем да пуснем, деактивираме или активираме, като използваме командата по-долу.
-- DROP Server scoped DDL Trigger
DROP TRIGGER TR_S_PREVENT_CREATEDATABASE ON ALL SERVER;
-- Disable Server scoped DDL Trigger
DISABLE TRIGGER TR_S_PREVENT_CREATEDATABASE ON ALL SERVER;
-- Enable Server scoped DDL Trigger
ENABLE TRIGGER TR_S_PREVENT_CREATEDATABASE ON ALL SERVER;
Целта на DDL тригерите
- За одит на всякакви DDL събития, случващи се на ниво база данни или сървър.
- За да предотвратите възникване на DDL събития на ниво база данни или сървър.
- За да предупреждава всеки път, когато DDL събития се случват на ниво база данни или сървър.
Задействания за влизане
Тригерите за влизане, както показва името, се изпълняват за събития LOGON в SQL Server. След като фазата на удостоверяване завърши за събитие за влизане, скриптът за задействане на LOGON ще се изпълни в допълнение към дейността за влизане. Ако влизането не е удостоверено успешно, тогава тригерите LOGON няма да бъдат задействани. Тригерите за влизане ще бъдат изброени в SSMS под секцията Тригери на сървърни обекти. Синтаксисът на тригера LOGON е както следва:
CREATE TRIGGER <schema_name.trigger_name>
ON ALL SERVER
{ FOR| AFTER } LOGON
AS { sql_statement [ ; ] [ ,...n ] | EXTERNAL NAME < method specifier > [ ; ] }
Създаване на тригери
Нека създадем прост тригер LOGON, за да уловим повече информация за събитието LOGON от функцията EVENTDATA(), както е показано по-долу.
CREATE TABLE Track_LOGON_EVENTS (EventData xml, PostDtm datetime)
GO
CREATE TRIGGER TR_LOGON
ON ALL SERVER
FOR LOGON
AS
BEGIN
INSERT INTO Track_LOGON_EVENTS
SELECT EVENTDATA(),GETDATE();
END
GO
Горният тригер LOGON ще улови всички подробности за дейност за влизане, подобна на това, което сме забелязали, докато използваме функцията EVENTDATA() в DDL Trigger. Трябва да внимаваме, докато планираме да използваме тригери LOGON, тъй като ако има някакви логически грешки вътре в тригера, това няма да позволи на никого или повечето потребители да се свържат с екземпляра на SQL Server.
За DROP, Деактивиране или Активиране на тригери LOGON, можем да използваме скрипта по-долу.
-- DROP LOGON Trigger
DROP TRIGGER TR_LOGON ON ALL SERVER;
-- Disable LOGON Trigger
DISABLE TRIGGER TR_LOGON ON ALL SERVER;
-- Enable LOGON Trigger
ENABLE TRIGGER TR_LOGON ON ALL SERVER;
Целта на тригерите за LOGON
- За да одитирате всички събития за LOGON, случващи се на сървъра.
- За да предотвратите възникването на събития за LOGON на сървъра
- За да предупреждава всеки път, когато на сървъра се случват събития за LOGON.
Свойства на тригера
sp_settriggerorder
sp_settriggerorder се използва за дефиниране на реда на изпълнение на тригера само за първото и последното задействане. Ако има повече от 2 DML задействания в таблица, да кажем 5 DML задействания, тогава можем да дефинираме първия DML тригер и последния DML тригер, но не можем да дефинираме реда на средните 3 задействания.
Забележка: Задаването на опцията ПЪРВА или ПОСЛЕДНА е специфично за конкретна категория Събитие за DML тригери. Например в таблица с 3 задействания INSERT, можем да дефинираме кой тригер INSERT е FIRST и кой INSERT тригер е LAST. Ако имате 3 задействания в таблица като INSERT, UPDATE и DELETE, тогава няма нужда да задавате условието за поръчка на задействане.
Синтаксисът за задаване на реда на задействане би бил следният:
exec sp_settriggerorder @triggername = '<trigger_schema_name.trigger_name>'
, @order = 'FIRST' | 'LAST'
, @stmttype = '<trigger event type>'
, @namespace = 'DATABASE' | 'SERVER' | 'NULL'
За DDL тригери можем да дефинираме първото и последното тригери с обхват на сървъра и след това да дефинираме първото и последното тригери с обхват на базата данни. Например, ако имаме 5 тригера с обхват на сървъра и 5 тригера с обхват на базата данни, тогава редът може да бъде дефиниран по следния начин:
- Първо задействане за DDL тригер с обхват на сървъра
- 3 други DDL задействания с обхват на сървъра в произволен ред
- Последно задействане за DDL тригер с обхват на сървъра.
- Първо задействане за DDL тригер с обхват на база данни (по едно на база данни)
- 3 други DDL задействания с обхват на базата данни в произволен ред
- Последно задействане за DDL тригер с обхват на база данни.
По отношение на настройката на първата или последната опция, тригерите DDL с обхват на базата данни могат да бъдат подредени в базата данни, а DDL тригерите с обхват на сървъра на ниво Инстанция.
Въпреки че SQL Server ни позволява да създаваме много тригери в таблица, се препоръчва внимателно да анализирате изискванията на тригера за по-добра поддръжка и отстраняване на неизправности.
Рекурсивни тригери
SQL Server също така поддържа рекурсивно извикване на тригери за DML тригери. Рекурсивните тригери могат да бъдат класифицирани като преки или непреки, както е показано по-долу.
Директно рекурсивни тригери – Потребителят или приложението актуализира запис в Таблица A. UPDATE Trigger A в Таблица A се задейства и актуализира таблица A отново. Тъй като записът в таблица A е актуализиран чрез Trigger, той отново ще извика UPDATE Trigger A и това ще се случи рекурсивно.
Нека създадем директни рекурсивни тригери в таблицата за продажби, използвайки следния скрипт:
CREATE TRIGGER TR_UPD_Recursive_Sales ON Sales
FOR UPDATE
AS
BEGIN
UPDATE Sales
SET SalesDate = GETDATE()
WHERE SalesId = (SELECT SalesId FROM Inserted)
END
GO
Изпълнете скрипта по-долу:
UPDATE Sales
SET SalesDate = GETDATE()
WHERE SalesId = 3;
Непреки рекурсивни тригери – Потребителят или приложението актуализира запис в таблица A. Тригерът UPDATE A в таблица A се задейства и актуализира запис в таблица B. Ако таблица B има тригер UPDATE за актуализиране на записите обратно към таблица A, тя ще извика тригера UPDATE в Таблица A, което ще се случи рекурсивно.
Нека създадем индиректен рекурсивен тригер върху таблици IDR_Test1 и IDR_Test2, използвайки скрипта по-долу:
DROP TABLE IDR_Test1
DROP TABLE IDR_Test2
CREATE TABLE IDR_Test1 (PK int NOT NULL);
GO
INSERT INTO IDR_Test1
values (10),(20)
GO
CREATE TABLE IDR_Test2 (PK int NOT NULL);
GO
INSERT INTO IDR_Test2
values (10),(20)
GO
CREATE TRIGGER TR_IDR_Test1
ON IDR_Test1
FOR UPDATE
AS
BEGIN
UPDATE IDR_Test2
SET PK = 30
WHERE PK IN (SELECT PK FROM inserted);
END
GO
CREATE TRIGGER TR_Temp2
ON IDR_Test2
FOR UPDATE
AS
BEGIN
UPDATE IDR_Test1
SET PK = 30
WHERE PK IN (SELECT PK FROM inserted);
END
GO
Изпълнете скрипта по-долу:
UPDATE IDR_Test1
SET PK = 1
WHERE PK = 10;
За да се избегнат тези видове извикване на рекурсивни тригери на ниво база данни, SQL Server има опция, наречена RECURSIVE_TRIGGERS на всяко ниво на база данни, за да прекъсне задействането на рекурсивния тригер. По подразбиране опцията за рекурсивно задействане е настроена на False за база данни. Активирайте само при необходимост след внимателно обмисляне на въздействието върху производителността или съответните промени в данните.
В SSMS щракнете с десния бутон върху нашата тестова база данни -> Изберете Properties -> Щракнете върху Опции и превъртете надолу, за да видите опцията за рекурсивни тригери е активирана или не, както е показано по-долу. За тестова база данни тя е зададена на False, тъй като False е стойността по подразбиране за опцията Recursive Triggers. За да включите опцията Рекурсивни тригери за конкретна база данни, просто щракнете върху падащата стойност, променете я на True и щракнете върху OK.
Чрез T-SQL можем да проверим опцията Recursive Trigger на тестовата база данни, като проверим колоната is_recursive_triggers_on от sys.databases DMV, както е показано по-долу.
select name, is_recursive_triggers_on
from sys.databases
where name = 'test'
За да променим опцията за рекурсивни тригери за база данни (Тест в моя пример), можем да изпълним скрипта по-долу.
ALTER DATABASE [Test] SET RECURSIVE_TRIGGERS ON WITH NO_WAIT
GO
За да го деактивирате обратно до фалшиво състояние (състояние по подразбиране) за база данни (Тест в моя пример), изпълнете скрипта по-долу.
ALTER DATABASE [Test] SET RECURSIVE_TRIGGERS OFF WITH NO_WAIT
GO
Вложени тригери
Рекурсивните тригери са класически пример за вложени тригери, но може да има няколко други случая, водещи до влагане на множество задействания. SQL Server позволява вмъкване на тригери до максимум 32 нива и всеки тригер, надвишаващ това ниво на влагане, ще бъде отменен от SQL Server. SQL Server има конфигурация за целия екземпляр за деактивиране на опцията вложени тригери. Моля, имайте предвид, че влагането на задействания на SQL Server с помощта на CLR код или управляван код не попада под ограничението от 32 нива, тъй като е извън обхвата на SQL Server. По подразбиране опцията за вложени тригери ще бъде активирана във всички екземпляри на SQL Server и можем да я деактивираме според изискванията.
Можем да проверим дали опцията за вложени тригери е активирана на ниво екземпляр в SSMS, като изпълним стъпките по-долу:
Щракнете с десния бутон върху Сървър -> Изберете Свойства -> Щракнете върху Разширени
За да деактивирате или изключите опцията за вложени тригери, щракнете върху падащото меню и го променете на False и щракнете върху OK .
Чрез T-SQL можем да проверим дали опцията вложени тригери е активирана, като проверим колоната value_in_use в sys.configurations DMV за име на конфигурация на вложени тригери.
За да деактивираме тази опция, трябва да използваме sp_configure системната съхранена процедура, както е показано по-долу:
EXEC sp_configure 'nested triggers', 0;
GO
RECONFIGURE;
GO
Във всеки DML или DDL тригери, за да се намери текущото ниво на вложение, SQL Server има вградена функция с име TRIGGER_NESTLEVEL за връщане на броя на тригерите, изпълнени за текущия израз, който е задействал тригера, включително самия него. Синтаксисът на функцията TRIGGER_NESTLEVEL би бил:
SELECT TRIGGER_NESTLEVEL ( object_id, <trigger_type> , <trigger_event_category> )
Когато object_id е идентификаторът на обекта на тригера, trigger_type ще бъде AFTER за задействане AFTER и IOT за INSTEAD OF тригер и trigger_event_category ще бъдат DML или DDL.
Например, ако трябва да разрешим само ниво на влагане до 10 и да увеличим грешката след 10 нива, тогава можем да го направим при тестов тригер като тук:
IF ((SELECT TRIGGER_NESTLEVEL(OBJECT_ID('test_trigger'), 'AFTER’, 'DML’)) > 10)
RAISERROR ('Trigger test_trigger nested more than 10 levels.',16, -1)
ШИФИРАНЕ
За шифроване на логиката или дефиницията на тригера, опцията WITH ENCRYPTION може да се използва в дефиницията на задействане, подобно на всички други обекти на SQL Server.
Клауза ИЗПЪЛНЯВАНЕ КАТО
За да се изпълни задействането с помощта на специфичен контекст за защита, клаузата EXECUTE AS може да се използва в дефиницията на тригера.
НЕ ЗА РЕПЛИКАЦИЯ
За да се идентифицира, че DML тригерът не трябва да се извиква, докато се изпълнява чрез промени в репликацията, свойството NOT FOR REPLICATION ще бъде зададено за всички обекти в базата данни за абонати.
Заключение
Благодарим ви, че преминахте през богатата статия за DDL тригери и тригери за влизане, където разбрахме целта на тригерите за DDL и влизане, как да създадем или пуснем, деактивираме или активираме тези тригери, както и как да използваме функцията EVENTDATA() за проследяване на DDL или влизане на дейности. В допълнение към това, ние научихме как да зададем реда на изпълнение на множество SQL DML или DDL тригери заедно с рекурсивни и вложени тригери в детайли и как да обработваме внимателно рекурсивни или вложени тригери.