介绍 (Introduction)

There is a lot of talk about how bad triggers are, how you should never use them, etc. I wanted to spend some time reviewing fact vs fiction and do an objective analysis of SQL Server database triggers (both DDL and DML), warts and all. We will review alternatives and compare them with triggers to determine advantages vs disadvantages of each approach.

关于如何使用不良触发器,如何使用它们,等等,有很多讨论。我想花一些时间回顾事实与虚构,并对SQL Server数据库触发器(DDL和DML),疣和所有。 我们将审查替代方案,并将其与触发因素进行比较,以确定每种方法的优缺点。

The following experiments will be conducted:

将进行以下实验:

  1. Triggers vs constraints will compare the performance of both solutions 触发与约束将比较两种解决方案的性能
  2. Historical records/auditing using triggers (trigger vs OUTPUT clause) 使用触发器的历史记录/审计(触发器与OUTPUT子句)
  3. SQL Profiler vs triggers SQL事件探查器与触发器
  4. DDL Triggers vs Extended events DDL触发器与扩展事件

入门 (Getting started)

1.触发与约束 (1. Trigger vs constraints)

The first example is the simplest one. We will compare a check constraint with a trigger to verify which one is faster.

第一个例子是最简单的例子。 我们将检查约束与触发器进行比较,以验证哪个更快。

We will first create a table named testconstraint that will check if the amount column has values below 1000:

我们首先将创建一个名为testconstraint的表,该表将检查amount列的值是否低于1000:


create table testconstraint
(id int PRIMARY KEY CLUSTERED,
amount int CONSTRAINT chkamount CHECK (amount <1000)
)

We will also create another table named testrigger, which will be similar to testconstraint, but it will use a trigger instead of a check constraint:

我们还将创建另一个名为testrigger的表,该表与testconstraint类似,但是它将使用触发器而不是检查约束:


create table testrigger
(id int PRIMARY KEY CLUSTERED,
amount int )

We will also create a trigger for the table to verify that the values inserted are lower than 1000:

我们还将为该表创建一个触发器,以验证插入的值是否小于1000:


CREATE TRIGGER amountchecker
ON dbo.testrigger
AFTER INSERT
AS
DECLARE @value int= (select TOP 1 inserted.amount from inserted)
IF @value >1000
BEGINRAISERROR ('The value cannot be higher than 1000', 16, 10);ROLLBACK
END
GO  

To compare the performance, we will set the STATISTICS IO and TIME:

为了比较性能,我们将设置STATISTICS IO和TIME:


SET STATISTICS io ON
SET STATISTICS time ON
GOinsert into testconstraint values(1,50)

This insert values, will have an execution time of 6 ms:

此插入值的执行时间为6毫秒:

Let’s try to insert a value above 1000:

让我们尝试插入一个大于1000的值:


insert into testconstraint values(2,8890)   

The execution time is 0 ms:

执行时间为0毫秒:

Now, let’s test the trigger:

现在,让我们测试一下触发器:


insert into testrigger values(1,50)

As you can see, the execution time is higher than a constraint:

如您所见,执行时间比约束要长:

If we try a value higher than 1000, the execution time is slightly higher (1 ms):

如果我们尝试大于1000的值,则执行时间会稍长一些(1 ms):


insert into testrigger values(2,5000)

To compare, we will insert one million rows in the table with constraints:

为了进行比较,我们将在具有约束的表中插入一百万行:


with testvaluesas(select 1 id, CAST(RAND(CHECKSUM(NEWID()))*1000 as int) pricesunion  allselect id + 1, CAST(RAND(CHECKSUM(NEWID()))*999 as int)  pricesfrom testvalueswhere id <= 1000000)insert into testconstraintselect *from testvaluesOPTION(MAXRECURSION 0)

After inserting a million rows, you can check the execution time:

插入一百万行后,您可以检查执行时间:

Then, we will do the same with the table with triggers:

然后,我们将对带有触发器的表执行相同的操作:


with testvaluesas(select 1 id, CAST(RAND(CHECKSUM(NEWID()))*1000 as int) pricesunion  allselect id + 1, CAST(RAND(CHECKSUM(NEWID()))*999 as int)  pricesfrom testvalueswhere id <= 1000000)insert into testriggerselect *from testvaluesOPTION(MAXRECURSION 0)

And check the execution time:

并检查执行时间:

The following table shows the results after running and truncating both tables 5 times each:

下表显示了将两个表分别运行和截断5次后的结果:

Execution time constraint (minutes:seconds) Execution time trigger (minutes:seconds) Difference in %
2:59 2:55 -2
2:37 3:14 19
2:37 2:45 5
2:45 2:45 0
2:35 2:58 13
Average 2 minutes :44 seconds Average: 2 minutes :55 seconds 7
执行时间限制(分钟:秒) 执行时间触发(分钟:秒) 差异%
2:59 2:55 -2
2:37 3:14 19
2:37 2:45 5
2:45 2:45 0
2:35 2:58 13
平均2分钟:44秒 平均:2分钟:55秒 7

As you can see, the constraint is faster. In this example constraints are 7% faster in average than triggers.

如您所见,约束更快。 在此示例中,约束条件平均比触发器要快7%。

In general, try to use primary keys and unique constraints to verify uniqueness, Default values to specify default values by default, foreign keys to verify the integrity between two tables and check constraints to check specific values. If none of these options works for your needs, maybe the computed columns can be an alternative. Computed columns are virtual columns not stored in a database based on expressions.

在一般情况下,尝试使用主键 S和唯一约束验证独特性, 默认值 ,默认情况下指定默认值, 外键 ,以验证两个表之间的完整性检查约束检查特定的值。 如果这些选项都不能满足您的需要,则可以使用计算列作为替代。 计算列是不基于表达式存储在数据库中的虚拟列。

2.使用触发器(trigger vs OUTPUT子句)进行历史记录跟踪(aka审计) (2. Historical record tracking (aka auditing) using triggers (trigger vs OUTPUT clause))

It is a common practice to use triggers to record changes in tables as a form of auditing. I saw some companies that are removing triggers and replacing it with stored procedures. They are using the OUTPUT clause. The output clause allows to capture inserted, deleted from INSERT, UPDATE, DELETE and MERGE operations.

通常使用触发器将表中的更改记录为审计的一种形式。 我看到一些公司正在删除触发器并将其替换为存储过程。 他们正在使用OUTPUT子句。 输出子句允许捕获从INSERT,UPDATE,DELETE和MERGE操作中插入,删除的内容。

We will create 2 tables. One to test the OUTPUT clause and another to test the trigger:

我们将创建2个表。 一个用于测试OUTPUT子句,另一个用于测试触发器:


create table sales2017output(productid int,price int)
create table sales2017trigger(productid int,price int)

In addition, we will create 2 historical tables to record the insert changes in the tables created above:

此外,我们将创建2个历史表来记录上面创建的表中的插入更改:


create table historicproductsalesoutput(productid int,price int, currentdate datetime default getdate())
create table historicproductsalestrigger(productid int,price int, currentdate datetime default getdate())   

We will create a trigger to insert the inserted value and the date of insertion in the historicproductsalestrigger table:

我们将创建一个触发器,将插入的值和插入日期插入historicalproductsalestrigger表中:


create trigger historicinsert
on dbo.sales2017triggerfor insert
asinsert into historicproductsalestriggerselect inserted.productid, inserted.price, getdate()from inserted
go

The trigger named historicinsert inserts data in the table historicproductsalestrigger when an insert occurs in the dbo.sales2017trigger.

当在dbo.sales2017trigger中发生插入时,名为historyinsert的触发器将在表historicalproductsalestrigger中插入数据。

Here it is how to store the historical records using the output clause (option 1):

这是使用output子句(选项1)存储历史记录的方法:


--Option 1insert into sales2017output OUTPUT inserted.productid, inserted.price, getdate()into historicproductsalesoutputvalues(1,22)

OUTPUT used the table inserted, which is a temporal table that stores the rows inserted.

OUTPUT使用插入的表,这是一个临时表,用于存储插入的行。

The second option uses triggers. When we do an insert, the historical records are created automatically by the trigger created before:

第二个选项使用触发器。 当我们执行插入操作时,历史记录将由之前创建的触发器自动创建:


--Option 2
insert into sales2017trigger values(1,22)

Let’s compare the performance. We will run insert one million rows using triggers:

让我们比较一下性能。 我们将使用触发器运行插入一百万行:


with testvaluesas(select 1 id, CAST(RAND(CHECKSUM(NEWID()))*1000 as int) pricesunion  allselect id + 1, CAST(RAND(CHECKSUM(NEWID()))*999 as int)  pricesfrom testvalueswhere id <= 1000000)insert into sales2017triggerselect *from testvaluesOPTION(MAXRECURSION 0)

To compare, we will insert one million rows using the OUTPUT Clause:

为了进行比较,我们将使用OUTPUT子句插入一百万行:


with testvaluesas(select 1 id, CAST(RAND(CHECKSUM(NEWID()))*1000 as int) pricesunion  allselect id + 1, CAST(RAND(CHECKSUM(NEWID()))*999 as int)  pricesfrom testvalueswhere id <= 1000000)INSERT into sales2017outputOUTPUT inserted.productid, inserted.price, getdate()into historicproductsalesoutputSELECT * From testvalues   OPTION(MAXRECURSION 0)

The following table shows the results after running several times:

下表显示了运行几次后的结果:

Execution time OUTPUT clause (minutes:seconds) Execution time trigger (minutes:seconds) Difference in %
5:56 3:52 34
6:05 3:42 39
5:58 3:56 34
5:46 3:48 34
6:01 3:51 36
Average 5 minutes and 57 seconds Average 3 minutes and 49 seconds Triggers are 35% faster
执行时间OUTPUT子句(分钟:秒) 执行时间触发(分钟:秒) 差异%
5:56 3:52 34
6:05 3:42 39
5:58 3:56 34
5:46 3:48 34
6:01 3:51 36
平均5分57秒 平均3分49秒 触发速度提高35%

As you can see, OUTPUT clause is slower than a trigger in some cases. If you need to created historical records of your table changes, be aware that replacing triggers with OUTPUT clause will not improve the performance. In the table, you can see that triggers are 35% faster than the OUTPUT clause.

如您所见,在某些情况下,OUTPUT子句比触发器慢。 如果需要为表更改创建历史记录,请注意,用OUTPUT子句替换触发器不会提高性能。 在表中,您可以看到触发器比OUTPUT子句快35%。

With OUTPUT, you have more control on the code, because you can extract the inserted and deleted rows in a specific stored procedure whenever you want. The problem with triggers is that they are executed even if you do not want them to. You can of course disable a trigger, but it is very common to activate triggers by accident.

使用OUTPUT,您可以对代码进行更多控制,因为您可以随时提取特定存储过程中插入和删除的行。 触发器的问题在于即使您不希望执行它们,它们也会被执行。 您当然可以禁用触发器 ,但是偶然激活触发器是很常见的。

Triggers can be a good choice if there is an external tool that access and inserts data to your database and you cannot access to code, but you need to add some functionality on insert, delete and update clauses.

如果有外部工具可以访问数据库并将数据插入数据库中并且您无法访问代码,那么触发器是一个不错的选择,但是您需要在插入,删除和更新子句中添加一些功能。

3. SQL事件探查器与触发器 (3. SQL Profiler vs trigger)

With SQL Profiler, you can store in a file or a table some SQL events like insert, update, create, drop and many other SQL Server events. Is SQL Profiler an alternative to triggers to track SQL Server events?

使用SQL Profiler,您可以在文件或表中存储一些SQL事件,例如插入,更新,创建,删除以及许多其他SQL Server事件。 SQL Profiler是否可以替代触发器来跟踪SQL Server事件?

In this example, we will track a table insert and store the result in a table.

在此示例中,我们将跟踪表插入并将结果存储在表中。

Let’s take a look to SQL Profiler:

让我们看一下SQL Profiler:

In the start menu, start the SQL Server Profiler:

在开始菜单中,启动SQL Server Profiler:

We will create a new template to store the T-SQL Statements completed. Go to File>Trace>New Trace:

我们将创建一个新模板来存储已完成的T-SQL语句。 转到文件>跟踪>新建跟踪:

Specify a name for the new template:

指定新模板的名称:

SQL:StmtCompleted will track the T-SQL Statements completed. Select this event:

SQL:StmtCompleted将跟踪已完成的T-SQL语句。 选择此事件:

You can specify filters by pressing the Column Filters button:

您可以通过按列过滤器按钮来指定过滤器:

In DatabaseName, we will only trace the SQL Statements of the AdventureWorks2016TCP3 database. You can use another database of your preference. Save the trace once that the database name is specified:

在DatabaseName中,我们将仅跟踪AdventureWorks2016TCP3数据库SQL语句。 您可以使用自己喜欢的另一个数据库。 指定数据库名称后,保存跟踪:

In the Profiler menu, go to New trace:

在“探查器”菜单中,转到“新建跟踪”:

Select the template just created above:

选择上面刚刚创建的模板:

In General tab, check the save to table option:

在“常规”选项卡中,选中“保存到表”选项:

Specify your Login and password.

指定您的登录名和密码。

Specify the database, schema and table name where you want to store the trace information. If the table does not exist, it will be created:

指定要在其中存储跟踪信息的数据库,架构和表名称。 如果该表不存在,将创建该表:

Run the trace, and to test in profiler, and in SSMS, go to Adventureworks2016CT3 Database or the database that you selected in the filter and run this statements:

运行跟踪,并在Profiler和SSMS中进行测试,请转到Adventureworks2016CT3数据库或在过滤器中选择的数据库,然后运行以下语句:


create table sales2017profiler(productid int,price int)
GO
insert into sales2017profiler
values (1,22)
GO

In SQL Server Profiler, you will be able to see the statement run including the application name where the statements run, the CPU time, duration, etc.:

在SQL Server Profiler中,您将能够看到语句运行,包括运行该语句的应用程序名称,CPU时间,持续时间等:

The problem with SQL Profiler is that it will be deprecated soon (for the database engine, but not for Analysis Services). According to Microsoft, SQL Profiler will be removed in a later version. Why?

SQL Profiler的问题在于它将很快被弃用 (对于数据库引擎,而不是Analysis Services)。 根据Microsoft的说法,SQL Profiler将在更高版本中删除。 为什么?

Because it consumes too many resources. SQL Profiler is recommended to run in another Server.

因为它消耗太多资源。 建议将SQL事件探查器运行在另一台服务器上。

In other words, it is not recommended to replace triggers with SQL Profiler. The best alternative to Profiler is extended events.

换句话说,不建议将触发器替换为SQL Profiler。 Profiler的最佳替代方法是扩展事件。

4. DDL触发器与扩展事件 (4. DDL Triggers vs Extended events)

DDL triggers are used to execute an action for events like creating a new database, altering a table, granting permissions.

DDL触发器用于对事件执行操作,例如创建新数据库,更改表,授予权限。

In this example, we will create a table in the master database to store the execution time, SQL User, Event and query executed. This table will store that information when a database is created using triggers.

在此示例中,我们将在master数据库中创建一个表来存储执行时间,SQL User,Event和已执行的查询。 当使用触发器创建数据库时,该表将存储该信息。

The table name will be trigger_event and it will store the information of the new databases created:

表名将为trigger_event,它将存储创建的新数据库的信息:


USE master
GO
CREATE TABLE trigger_event (ExecutionTime datetime, [User] nvarchar(80), EventType nvarchar(30), Query nvarchar(3000));
GO  

The following trigger stores the user, query, execution date and event when a database is created in the trigger_event table created above:

在上面创建的trigger_event表中创建数据库时,以下触发器存储用户,查询,执行日期和事件:


CREATE TRIGGER DATABASECREATED
ON ALL SERVER
FOR CREATE_DATABASE
AS   DECLARE @data XML
SET @data = EVENTDATA()
INSERT trigger_event   VALUES   (GETDATE(),   CONVERT(nvarchar(80), CURRENT_USER),   @data.value('(/EVENT_INSTANCE/EventType)[1]', 'nvarchar(30)'),   @data.value('(/EVENT_INSTANCE/TSQLCommand)[1]', 'nvarchar(3000)') ) ;
GO  

EVENTDATA() is a function complementary to triggers that stores the trigger event information using a XML file.

EVENTDATA()是对触发器的补充,该函数使用XML文件存储触发器事件信息。

To test the trigger we will create a database named test9:

为了测试触发器,我们将创建一个名为test9的数据库:


create database test9

If we do a select in the trigger_event table, we notice that all the information was stored after the create database statement:

如果在trigger_event表中进行选择,则会注意到所有信息都存储在create database语句之后:


select * from trigger_event

The values displayed are the following:

显示的值如下:

An alternative to triggers is Extended Events. As we said before, SQL Profiler will be removed in the future and the Extended Events will replace them.

触发器的替代方法是扩展事件。 如前所述,将来会删除SQL Profiler,而扩展事件将替换它们。

In this example, we will create an extended event to detect if a new database was created.

在此示例中,我们将创建一个扩展事件以检测是否创建了新数据库。

In the SSMS, go to Management>Session and right click and select new session:

在SSMS中,转到“管理”>“会话”,然后右键单击并选择新的会话:

Specify a session name and check the Start the event session immediately after session created and Watch live data on screen as it is captured option:

指定会话名称,并选中创建会话后立即启动事件会话和“在捕获时在屏幕上观看实时数据”选项:

In event library, select the database_created event:

在事件库中,选择database_created事件:

On the Session created, right click and select Watch Live Data:

在创建的会话上,右键单击并选择“监视实时数据”:

To generate an event, create a database:

要生成事件,请创建一个数据库:


create database test3

For some reason, the event appears after a second event:

由于某种原因,该事件在第二个事件之后出现:


Create database test4

You will now be able to see the first event:

现在,您将能够看到第一个事件:

Extended Events is a great alternative to triggers. It is simple and it does not consume as much resources like SQL Profiler.

扩展事件是触发器的绝佳替代选择。 它很简单,并且不会像SQL Profiler那样消耗太多资源。

结论 (Conclusions)

We learned the following:

我们了解了以下内容:

  • Constraints should be used whenever is possible instead of triggers because they are faster, they are easier to maintain and require less code. 应尽可能使用约束代替触发器,因为它们更快,更易于维护并且需要更少的代码。
  • OUTPUT parameters in SQL stored procedures do not have better performance than the triggers, based on the tests I conducted. Therefore, it is not a good choice to replace triggers for performance reasons. 根据我进行的测试,SQL存储过程中的OUTPUT参数没有比触发器更好的性能。 因此,出于性能原因,更换触发器不是一个好的选择。
  • SQL Profiler should not be used to replace triggers, because it is a feature that will be removed soon. SQL Profiler不应用于替换触发器,因为它将很快被删除。
  • Extended events can be a good choice to replace DDL triggers in some scenarios. 在某些情况下,扩展事件可以替代DDL触发器。

There are some DBAs that say that we should never use triggers, but if it does not affect the performance of your code, if they are not used all the time, it is not bad to use it in your code. Make sure to disable for massive bulks and be careful with recursive triggers. Triggers are not evil, but they must be used wisely.

有一些DBA说我们永远不应该使用触发器,但是如果它不影响代码的性能,那么如果一直不使用它们,那么在代码中使用它也不错。 确保禁用大量批量,并小心递归触发器。 触发器不是邪恶的,但是必须明智地使用它们。

Always remember:

总记得:

“With great triggers comes great responsibility”.

“伟大的触发因素带来了重大的责任”。

参考资料 (References)

For more information, refer to these links:

有关更多信息,请参考以下链接:

  • Extended Events Catalog Views (Transact-SQL) 扩展事件目录视图(Transact-SQL)
  • Using Extended Events to review SQL Server failed logins 使用扩展事件查看SQL Server登录失败
  • CREATE TRIGGER (Transact-SQL) 创建触发器(Transact-SQL)
  • SQL Server Profiler SQL Server探查器

翻译自: https://www.sqlshack.com/are-sql-server-database-triggers-evil/

SQL Server数据库是否会引发恶意?相关推荐

  1. SQL SERVER数据库datediff函数引发的性能问题

    今天,一哥们反馈系统很慢,很卡,让我远程看一下.我远程过去查了一下数据库系统,发现很多阻塞,语句都基本相似,并且表的数据也不大,只有10多万条记录. 1.问题分析: 本系统是sql server数据库 ...

  2. mysql raiserror_RAISERROR在SQL Server数据库中的用法

    raiserror  是由单词 raise error 组成 raise  增加; 提高; 提升 raiserror 的作用: raiserror 是用于抛出一个错误.[ 以下资料来源于sql ser ...

  3. SQL Server数据库字段类型详解

    1.字符串类型 Char char数据类型用来存储指定长度的定长非统一编码型的数据.当定义一列为此类型时,你必须指定列长.当你总能知道要存储的数据的长度时,此数据类型很有用.例如,当你按邮政编码加4个 ...

  4. 浅谈SQL Server 数据库的触发器

    浅谈SQL Server 数据库的触发器   触发器的特征: 1.触发器是在对表进行增.删.改时,自动执行的存储过程.触发器常用于强制业务规则,它是一种高级约束,通过事件进行触发而被执行. 2.触发器 ...

  5. 如何记录SQL Server数据库对象

    介绍 (Introduction) In any good programming reference, you will read that a developer has to document ...

  6. 如何下载和安装SQL Server数据库实验助手(DEA v2.0)

    介绍 (Introduction) I had the opportunity to work and perform various tests with Database Experimentat ...

  7. SQL Server数据库连续集成(CI)最佳实践以及如何实现它们–测试,处理和自动化

    测试中 (Testing) Test databases should be processed with unit tests In many shops code is unit tested a ...

  8. iis网站服务器+sql server数据库服务器安全

    iis网站服务器+sql server数据库服务器安全 一 程序部分注意事项 1 友好的错误提示页,不出错误黄页,会暴露信息 2 输入参数检测,get,post,cookie验证,防注入 3 页面层不 ...

  9. sql server数据库中raiserror函数的用法1

    server数据库中raiserror的作用就和asp.net中的throw new Exception一样,用于抛出一个异常或错误.这个错误可以被程序捕捉到. raiserror的常用格式如下: r ...

最新文章

  1. mysql保存数据提示:Out of range value for column错误
  2. 【操作系统】RedHat7系安装显卡驱动
  3. python画海绵宝宝_一步一步教你画章鱼哥怎么画好看?教你学画海绵宝宝的章鱼哥简笔画!...
  4. 假期第一次编程总结(改二)
  5. 旅游捞金的六大方式,玩着把钱赚了
  6. html5学习之canvas模块的简单使用,作画三角形、圆形、矩形等
  7. node.js(二)创建服务器
  8. linux的使用 --- 虚拟机创建CentOS(Intel VT-X)
  9. SOTA级发丝抠图模型PP-Matting开源,支持多场景精细化分割
  10. java.math.BigDecimal详解及加减乘除计算
  11. 【STM32F429的DSP教程】第13章 DSP快速计算函数-三角函数和平方根
  12. 计算机竞赛游戏探险岛,冒险岛2主线任务攻略_第三章主线任务图文攻略
  13. 思维方式 | 深入浅出解释“第一性原理”
  14. Office Visio 2013、Office Project Pro 2013 简体中文大客户版32位、64位下载
  15. 去掉word中的页眉
  16. matlab2016与VS2019混合编程
  17. antd 给input设置值_React中input框设置value报错解析
  18. jsp之bootstrap-datetimepicker日期插件
  19. js室内地图开发_室内地图 JavaScript API
  20. Python学习 matplotlib库 霍兰德人格分析雷达图

热门文章

  1. Github简单使用 - 更新项目
  2. 软件工程(2018)第三次团队作业
  3. linux 常识笔记 20160621
  4. android学习之路1:前车之鉴之开发环境搭建
  5. 我晕,代码又写错了,操你大爷
  6. 弄懂bind,apply和call的区别
  7. ★LeetCode(784)——字母大小写全排列(JavaScript)
  8. 计算机网络学习笔记(16. 计算机网络与Internet发展历史)
  9. vuetify中文文档_我们为什么选择Vuetify作为前端框架
  10. 都有数据一直报空指针_C语言指针难吗?纸老虎而已,纯干货讲解(附代码)...