记一次由mysql触发器引发的故障
admin
2023-05-10 13:41:47
0次

上周六到公司上班,刚坐下没多久,公司业务传过消息说,用户borrow表信息无法更新。查看网站报错如下:
记一次由mysql触发器引发的故障
报错信息表示是由于mysql的函数和触发器引起的,问了下公司开发,他们表示函数功能已经测试上线好久了,没有问题,而触发器是这周刚上的。于是,赶紧进入生产的DB服务器进行查看:

mysql> use wendi;
Database changed
mysql> SHOW TRIGGERS\G;
...
*************************** 2. row ***************************
             Trigger: cl_borrow_before_insert_tigger
               Event: INSERT
               Table: cl_borrow
           Statement: begin
  set @channel_id = (select channel_id from cl_user where user_id = new.user_id);
  -- if @channel_id is not null and new.channel_id is null THEN
  --   update cl_borrow set channel_id=@channel_id where borrow_id = new.borrow_id;
  -- end if;
  insert into cl_borrow_status_log (user_id,borrow_id,status_old,status_new,audit_user_id,audit_remark,create_time,channel_id) values (new.user_id,new.borrow_id,null,new.status,new.audit_user_id,new.audit_remark,UNIX_TIMESTAMP(now()),@channel_id);
end
              Timing: BEFORE
             Created: NULL
            sql_mode: STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION
             Definer: root@%
character_set_client: utf8
collation_connection: utf8_general_ci
  Database Collation: utf8_general_ci
*************************** 3. row ***************************
             Trigger: cl_borrow_after_insert_trigger
               Event: INSERT
               Table: cl_borrow
           Statement: begin
  set @channel_id = (select channel_id from cl_user where user_id = new.user_id);
  -- if @channel_id is not null and new.channel_id is null THEN
  --   update cl_borrow set channel_id=@channel_id where borrow_id = new.borrow_id;
  -- end if;
  insert into cl_borrow_status_log (user_id,borrow_id,status_old,status_new,audit_user_id,audit_remark,create_time,channel_id) values (new.user_id,new.borrow_id,null,new.status,new.audit_user_id,new.audit_remark,UNIX_TIMESTAMP(now()),@channel_id);
end
              Timing: AFTER
             Created: NULL
            sql_mode: STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION
             Definer: root@%
character_set_client: utf8
collation_connection: utf8_general_ci
  Database Collation: utf8_general_ci
*************************** 4. row ***************************
             Trigger: cl_borrow_after_update_trigger
               Event: UPDATE
               Table: cl_borrow
           Statement: begin
  if old.status != new.status then 
    set @channel_id = (select channel_id from cl_user where user_id = new.user_id); 
    insert into cl_borrow_status_log (user_id,borrow_id,status_old,status_new,audit_user_id,audit_remark,create_time,channel_id) values (new.user_id,new.borrow_id,old.status,new.status,new.audit_user_id,new.audit_remark,UNIX_TIMESTAMP(now()),@channel_id);
  end if;
end
              Timing: AFTER
             Created: NULL
            sql_mode: STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION
             Definer: root@%
character_set_client: utf8
collation_connection: utf8_general_ci
  Database Collation: utf8_general_ci
*************************** 5. row ***************************
             Trigger: cl_borrow_status_log
               Event: INSERT
               Table: cl_borrow_status_log
           Statement: BEGIN
  update cl_borrow set double_audit_user_id = new.audit_user_id,double_audit_time=new.create_time where borrow_id=new.borrow_id ;
end
              Timing: AFTER
             Created: NULL
            sql_mode: STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION
             Definer: root@%
character_set_client: utf8
collation_connection: utf8_general_ci
  Database Collation: utf8_general_ci
    ...
    11 rows in set (0.00 sec)

如上,总共有11条触发器。为了不影响业务,我决定先将触发器备份,然后将其删除。

1,备份mysql触发器:
mysqldump --triggers -R -ndt -uroot -p cashloan> wenditrigger.sql

这里复习下mysqldump命令:

--triggers: Dump triggers for each dumped table. (Defaults to on; use --skip-triggers to disable.)

这个是默认值,mysqldump默认会导出触发器。(如果不想备份触发器使用--skip-triggers即可)

-R, --routines: Dump stored routines (functions and procedures).

导出存储过程以及函数。

-n, --no-create-db Suppress the CREATE DATABASE ... IF EXISTS statement that normally is output for each dumped database if --all-databases or --databases is given.

不创建建库语句,只对数据进行导出。

-d, --no-data No row information.

不导出数据,只导出表结构。

-t, --no-create-info Don't write table creation info.

不导出建表语句,只导出数据。

2,查看备份内容:
[root@DB ~]$ less wenditrigger.sql
-- MySQL dump 10.13  Distrib 5.6.20, for linux-glibc2.5 (x86_64)
...
DELIMITER ;;
/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`%`*/ /*!50003 TRIGGER `__test_trigger_update` AFTER INSERT ON `__test` FOR EACH ROW begin
  if new.user_id=100 THEN
    update __test set tian='@@@@' where id=new.id;
  end if;
  insert into __test2 (id,tian,user_id) values (new.id,new.tian,new.user_id);
end */;;
...
/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`%`*/ /*!50003 TRIGGER `cl_borrow_before_insert_tigger` BEFORE INSERT ON `cl_borrow` FOR EACH ROW begin
  set @channel_id = (select channel_id from cl_user where user_id = new.user_id);
  -- if @channel_id is not null and new.channel_id is null THEN
  --   update cl_borrow set channel_id=@channel_id where borrow_id = new.borrow_id;
  -- end if;
  insert into cl_borrow_status_log (user_id,borrow_id,status_old,status_new,audit_user_id,audit_remark,create_time,channel_id) values (new.user_id,new.borrow_id,null,new.status,new.audit_user_id,new.audit_remark,UNIX_TIMESTAMP(now()),@channel_id);
end */;;
DELIMITER ;

可以看到触发器已经备份好了。

3,删除触发器:

因为当时情况紧急,首要任务是将业务恢复,所以就把触发器全部删除了。
删除暂时没找到批量的方法,还好数据只有11条,一条一条删吧。

...
mysql> drop trigger cl_borrow_after_insert_trigger;
mysql> drop trigger cl_borrow_after_update_trigger;
mysql> drop trigger cl_borrow_status_log;
mysql> drop trigger cl_installment_after_insert_trigger;
...

至此,业务终于恢复了。

小结:

1,MySQL触发器属于隐式调用,往往会在你不知道的情况下做出许多操作,从而增加系统的复杂程度。
2,复杂MySQL触发器会嵌套使用,这就有可能产生死锁,本例就是个印证,borrow表触发插入其他表,而插入其他表的操作又会触发borrow表更新,这就产生了死锁,导致borrow表无法被更新。

MySQL触发器简介:

触发器是一种与表操作有关的数据库对象,当触发器所在表上出现指定事件时,将调用该对象,即表的操作事件触发表上的触发器的执行。
触发器语法:

CREATE TRIGGER trigger_name
trigger_time
trigger_event ON table_name
FOR EACH ROW
trigger_statement

trigger_name:触发器名称
trigger_time:触发器触发时机(BEFORE/AFTER)
trigger_event: 触发事件(INSERT,UPDATE,DELETE)
table_name: 建立触发器的表名称
trigger_statement: 触发器程序体,可以为单一的SQL语句,也可以是包含BEGIN,END在内的多条语句。
FOR EACH ROW: 行级触发

参考文章:
https://www.cnblogs.com/duodushu/p/5446384.html

相关内容

热门资讯

空调连续工作多久需要停机休息 空调连续工作的时间取决于多个因素,包括空调的型号、功率、使用环境和维护情况等。一般来说,空调可以连续...
空调哪个符号是除湿的 空调除湿功能通常用一个水滴形状的符号表示。不同品牌和型号的空调遥控器可能会有所差异,但一般来说,除湿...
格力空调多长时间加一次氟利昂 1、一般情况下,空调对氟利昂的使用量是非常小的,所以在空调使用了四年左右的时候再考虑加也没问题。2、...
空调用多长时间需要加氟一次 一般情况下是五年。当出现以下这几种情况时,空调才需要加氟。1、空调的室内机和室外机各有两个接口,当接...
格调生活·C标思享荟:卡萨帝在... 9月12日的郑州龙湖金融岛,夜风轻拂,灯光璀璨。“C标时刻共传奇——卡萨帝思享荟暨20周年品牌盛典郑...
品牌价值破千亿、高端份额持续领... 9月12日,“C标时刻共传奇——卡萨帝思享荟暨20周年品牌盛典郑州站”在郑州龙湖金融岛举办。2026...
空调主板坏了修800还是换新的 空调主板坏了是修还是换新,需要考虑多个因素,以下是一些建议:1. 维修成本:如果维修费用较低,比如 ...
空调开多久需要休息多长时间 问题:空调开多久需要休息多长时间回答:1、在使用空调的过程中,一般空调开7~8个小时左右需要休息2~...
中央空调多长时间清洗一次 1、一般空调开机前清洗一次即可,空调开机中间时段再清洗一次,空调关机时清洗一次,这样比较合理。2、空...
中央空调氟利昂多久加一次 看空调制热和制冷程度,很多人的观点认为氟利昂必须一年放一次,其实这个观点是错误的,理解上面有点偏差,...