[原]MySQL触发器使用心得(解决表间循环触发问题)

刘付杰 18/11/26 17:03:20

触发器简介

  • 什么是触发器
    触发器这个名字实际上起的非常生动了,就是指的在数据库系统中发生某一个动作(比如增删改都是可选的动作)时自动触发预先设置好的另外一个动作,包括但不限于基本的增删改,再配合IF ELSE语句可以实现很多非常强大的功能。
  • 触发器的优点
    触发器的优点在于,可以使用简单的配置来实现复杂的功能,这些功能如果想要在应用层面去实现往往需要比较多的代码量。
  • 触发器的缺点
  1. 触发器有一个显而易见的缺点,尤其是在MySQL中,由于MySQL仅支持行触发模式,假如数据库系统中现在有一条触发器的功能是:当前表有新数据插入时,将数据同时插入另外一张表,此时,如果我们使用INSERT语句一次性向当前表中插入一千条数据(使用类似于INSERT INTO cur_table(id,name) VALUES(1,’周一‘) (2,'周二')....(1000,'周一千')的批量插入语句),由于行触发的缘故,我们设定的插入触发器将会被触发1000次,这将带来严重的性能问题! 因此,在对性能有要求的场景下不要使用触发器,如果一定要使用,务必保证触发器中的语句的开销不会很大,并且不会被频繁触发!
  2. 触发器还有一个缺陷,就是会将业务和数据库捆绑在一起,使得系统的耦合度变高,当业务需求有变更时,系统可能变得难以维护。因此,在业务需求不稳定或者维护人员频繁变动的场景下尽量不要使用触发器。

MySQL中触发器的创建

先给一个示例
假设数据库中有两张表,分别为table1table2,两张表结构一致,都是有两个名为col1col2的字段,如下

col1 col2

table1中创建一个触发器(SQL语句)

CREATE TRIGGER `insert_into_table2_too` AFTER INSERT ON `table1`
FOR EACH ROW 
BEGIN
	INSERT INTO `table2` (`col1`,`col2`) VALUES(NEW.`col1`,NEW.`col2`);
END

上述SQL语句在table1中新建了一个名为insert_into_table2_too的触发器,这个触发器会在表table1中有新数据插入之后(AFTER),执行BEGINEND之间的SQL语句。
第二行的FOR EACH ROW指的就是文章前面提到的行触发,因为MySQL只支持行触发,所以创建触发器时一定要加上这一句。
再来看BEGINEND之间的语句,这里需要介绍触发器中两个关键字,分别是NEWOLD,这两个关键字在不同的触发器触发条件(INSERTUPTATEDELETE)中的含义略微不同,具体如下:
(摘抄自:https://www.cnblogs.com/geaozhang/p/6819648.html)

  1. 在INSERT型触发器中,NEW用来表示将要(BEFORE)或已经(AFTER)插入的新数据;
  2. 在UPDATE型触发器中,OLD用来表示将要或已经被修改的原数据,NEW用来表示将要或已经修改为的新数据;
  3. 在DELETE型触发器中,OLD用来表示将要或已经被删除的原数据;在DELETE型触发器中,OLD用来表示将要或已经被删除的原数据。

在这里这行代码中,NEW就代表着刚刚被插入到table1中的那一行数据。那么NEW.col1就代表着刚刚被插入的col1字段。
一个简单的触发器这样一来就可以工作了。如果BEGINEND语句之间只有一句SQL语句,那么BEGINEND可以省略不写

解决触发器的表间循环触发

  1. 问题背景

假设现在有一个奇怪的需求(我就遇到了这样的需求),需要将表table1col1字段的修改同步到表table2,同时需要将表table2中的col2字段的修改同步到table1
如果我们定义以下两个触发器:

table1中定义:

CREATE TRIGGER `sync_col1_to_table2` AFTER UPDATE ON `table1`
FOR EACH ROW 
	UPDATE `table2` SET `col1` = NEW.`col1` WHERE XXX;

table2中定义:

CREATE TRIGGER `sync_col2_to_table1` AFTER UPDATE ON `table2`
FOR EACH ROW 
	UPDATE `table1` SET `col2` = NEW.`col2` WHERE XXX;

表面看起来似乎很美好,但是这时候如果我们尝试去修改table1或者table2中的任一字段,数据库会报错:

Error: Can't update table 'table2' in stored function/trigger 
because it is already used by statement which invoked this stored function/trigger.

这个错误的意思就是,在执行修改的时候触发一个触发器,而触发器中会执行另外一个UPDATE语句,会接着触发另一个触发器… 从而形成循环触发,因此执行失败。
产生这个错误的原因是触发器的操作粒度过大,我们在触发器中仅仅想更改表中的一个字段,但是直接使用UPDATE语句的操作粒度是一整行,因此导致循环触发。
拿第一个触发器来讲,我们的本意是,当table1中的col2字段更新时,不应该触发UPDATEtable2SETcol1= NEW.col1WHERE XXX;这个操作(也就是我们期望字段粒度的触发器) ,但是事实上,table2中的任何一个字段被UPDATE,都会触发预先设定好的UPDATE类型触发器,也就是刚才所说的行粒度

  1. 解决方案

为了解决这个问题,我们需要请出SQL中的分支控制语句,也就是我们常用的IF ELSE语句,通过IF语句来判断某一个字段的值是否更新,选择性的执行对应的UPDATE操作,从而实现字段粒度的触发器。

SQL代码如下:
table1中:

CREATE TRIGGER `sync_col1_to_table2` AFTER UPDATE ON `table1`
FOR EACH ROW 
BEGIN
	IF NEW.col1 != OLD.col1 THEN
		UPDATE `table2` SET `col1` = NEW.`col1` WHERE XXX;
	END IF;
END;

table2中:

CREATE TRIGGER `sync_col2_to_table1` AFTER UPDATE ON `table2`
FOR EACH ROW 
BEGIN
	IF NEW.col2 != OLD.col2 THEN
		UPDATE `table1` SET `col2` = NEW.`col2` WHERE XXX;
	END IF;
END;

这样一来,当table1中的col1字段有更新时,触发器经过判断,新旧字段值不一样,因此调用UPDATEtable2SETcol1= NEW.col1WHERE XXX;语句更新table2中的col1字段,这一调用同时会触发table2上的触发器,但是这个触发器经过IF语句的判断,发现新旧col2的值是一样的,因此不会调用下面的UPDATE语句,如此便实现了字段粒度的触发器,不会再造成表间循环触发的问题。

最后

本文只是从一部分方面简单介绍了触发器的概念以及应用,触发器还有很多额外的知识需要读者另行学习掌握。

作者:fujie2017 发表于 2018/11/26 17:03:20 原文链接 https://blog.csdn.net/fujie2017/article/details/84532399
阅读:45