mysql如何临时禁用触发器

mysql如何临时禁用触发器

###起因   Mysql的触发器,在触发控制上,只能按照对数据的操作方式(Insert,Update,Delete)以及操作前后(before,after)进行触发控制。但是如果碰到以下需求又该如何:对于A表的Insert语句,只有符合某些条件的数据触发Insert触发器。

###自己当初条件反射的写法   在对应的触发器语句中,增加条件判断的逻辑。举个栗子: 有个用户信息表user,有个通讯录表addressbook,两张表表结构类似,业务需求上某些数据需要做数据实时同步,即user有更新,addressbook也要更新。用户表有个Insert触发器,只把性别为男的用户数据插入到addressbook中。 两表结构如下:

#为方便起见两个表结构就一模一样了,真实环境下一般都是部分字段类似而已
#性别字段取值1男0女 
CREATE TABLE `user` (
	`id` INT(11) NOT NULL AUTO_INCREMENT,
	`name` VARCHAR(50) NULL DEFAULT NULL,
	`sex` TINYINT(4) NULL DEFAULT '0',
	`age` INT(11) NULL DEFAULT '0',
	`phone` VARCHAR(50) NULL DEFAULT NULL,
	`qq` VARCHAR(50) NULL DEFAULT NULL,
	PRIMARY KEY (`id`)
)
;

CREATE TABLE `addressbook` (
	`id` INT(11) NOT NULL AUTO_INCREMENT,
	`name` VARCHAR(50) NULL DEFAULT NULL,
	`sex` TINYINT(4) NULL DEFAULT '0',
	`age` INT(11) NULL DEFAULT '0',
	`phone` VARCHAR(50) NULL DEFAULT NULL,
	`qq` VARCHAR(50) NULL DEFAULT NULL,
	PRIMARY KEY (`id`)
)
;

此时user表的Insert触发器写法:

CREATE DEFINER=`root`@`localhost` TRIGGER `user_insert_trigger` BEFORE INSERT ON `user` FOR EACH ROW 

BEGIN
	if new.sex = 1 
	then 
		insert into addressbook (name,sex,age,qq,phone) 
			values (new.name,new.sex,new.age,new.qq,new.phone)
		;
	end if;
	
END

  经常码代码的程序猿们,应该最先想到的是这种写法吧,毕竟符合咱们平常写代码的逻辑。但是需求往往不是那么简单的,这头怪兽总是会向着我们不可预期的方向发展。再举个栗子: 现在只要同步这些用户:年龄大于30岁,qq号小于8位的,使用189手机的男用户。(卧槽(╯‵□′)╯︵┻━┻)。现在的触发器语句可能是这样子:

CREATE DEFINER=`root`@`localhost` TRIGGER `user_insert_trigger` BEFORE INSERT ON `user` FOR EACH ROW BEGIN
	if new.sex = 1 and  substring(new.phone,1,3)=189 and length(new.qq) <=8 and new.age >30
	then 
		insert into addressbook (name,sex,age,qq,phone) 
		values (new.name,new.sex,new.age,new.qq,new.phone)
			 ;
	end if;
END

  不就是if多了几个and条件,多使用了几个sql函数而已嘛!这的是这样吗?如果手机的判断条件变成是“福建电信用户”(不止是189号头哦)呢?还有更奇葩的需求:如果要实现user表和addressbook表双向同步怎么办,没错,就是user表跟addressbook表都有insert触发器往对方表插数据,这在mysql下是不允许的(防止循环触发)。当时碰到这个需求我竟然脑袋一热也用上面的方法这么写,触发器插入前判断new的数据在要插入的表里存不存在orz。 总结一下,上面的触发器写法有很多缺点:

  1. 当对触发的数据有过滤需求时,需要用数据库语言写很多条件判断语句,有些在程序语言中很好实现的,在数据库语言中非常困难。
  2. 不利于理解与维护。由于有了1中那么多逻辑判断代码,导致触发器的语句会很冗长且复杂,阅读起来非常困难,即使加了注释。
  3. 不利于调试。数据库语言毕竟不像其他程序设计语言,你想打断点或者输出日志看是什么原因没有触发吗?图样,只能将insert数据插入到一张临时表里查看。
  4. 如果两张表有互相insert触发往对方表增加新数据的逻辑,这种写法要么不可行,要么作死。

###更灵活优雅的写法   方法是使用mysql的session级变量控制触发器触发,然后把上面的一堆判断逻辑放到程序代码中。这是在google上找到的stackoverflow帖子,感觉挺有用的。 举个栗子,上面的触发器可以这么写:

CREATE DEFINER=`root`@`localhost` TRIGGER `user_insert_trigger` BEFORE INSERT ON `user` FOR EACH ROW 

#这里@disable_triggers 是自定义的session变量(mysql中约定session变量用@开头,只对某一次会话有效,不影响其他会话),保证使用时各个触发器名字不一样就好
BEGIN
	IF @disable_triggers IS NULL THEN
		insert into addressbook (name,sex,age,qq,phone) 
		values (new.name,new.sex,new.age,new.qq,new.phone)
		;
	END IF;
	
END

  如果在使用insert语句时,不想触发触发器,在sql语句前后加上这么两句即可。

#变量设为非NULL,这样不会进入触发器的相关操作
SET @disable_triggers = 1;

#不打算触发触发器的insert语句
insert into user values(....);

#上面语句执行完毕完毕后,重新将变量设置为NULL,重新开启触发逻辑
SET @disable_triggers = NULL;

  对于某些更新数据时暂时想禁用触发器的情况,这种方法就可以先在程序代码中实现判断,然后决定要不要在执行的sql语句前后加入SET @disable_triggers = 1;SET @disable_triggers = NULL; 来临时禁止触发器触发操作,触发器的代码就可以做到非常简洁而且容易维护。将判断逻辑放到程序代码中也方便debug。

  不过在实践中发现此方法要注意以下几点(我使用的开发语言为java):

  1. SET @disable_triggers = 1; SET @disable_triggers = NULL; 这两个语句必须与要执行的sql放在同一条sql中,然后一起提交执行。不能先 执行 SET @disable_triggers = 1; 再执行数据更新语句,再执行SET @disable_triggers = NULL; ,因为使用的变量是session变量,只有在同一个会话中,触发器才能找到disable_triggers变量。如果分成三次提交执行,相当于三个会话,触发器那边得到的disable_triggers变量还是为NULL。
  2. 对于java中的 sql prepared statement 预处理执行sql语句的方式,以上设置session变量的方法会抛异常。(预处理模式不支持一次同时提交多个sql语句?)我在项目中使用的是spring 的jdbctemplate,原生jdbc应该也是一样的。所以使用这种方法临时禁用触发器时,还是老实做好参数防注入判断,然后拼接sql语句执行吧,还好这种需求的场景不会很多。

转载于:https://my.oschina.net/u/1010578/blog/379416

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值