mysql如何臨時禁用觸發器

mysql如何臨時禁用觸發器

###原由   Mysql的觸發器,在觸發控制上,只能按照對數據的操做方式(Insert,Update,Delete)以及操做先後(before,after)進行觸發控制。可是若是碰到如下需求又該如何:對於A表的Insert語句,只有符合某些條件的數據觸發Insert觸發器。java

###本身當初條件反射的寫法   在對應的觸發器語句中,增長條件判斷的邏輯。舉個栗子: 有個用戶信息表user,有個通信錄表addressbook,兩張表表結構相似,業務需求上某些數據須要作數據實時同步,即user有更新,addressbook也要更新。用戶表有個Insert觸發器,只把性別爲男的用戶數據插入到addressbook中。 兩表結構以下:mysql

#爲方便起見兩個表結構就如出一轍了,真實環境下通常都是部分字段相似而已
#性別字段取值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觸發器寫法:spring

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手機的男用戶。(臥槽(╯‵□′)╯︵┻━┻)。如今的觸發器語句多是這樣子:sql

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帖子,感受挺有用的。 舉個栗子,上面的觸發器能夠這麼寫:session

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。google

  不過在實踐中發現此方法要注意如下幾點(我使用的開發語言爲java):debug

  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語句執行吧,還好這種需求的場景不會不少。
相關文章
相關標籤/搜索