使用触发器记录表内数据变更日志

使用触发器记录表内数据变更日志

一、遇到的问题

因为系统升级改造,遇到的问题是,新老两个数据库的两张表需要做数据同步,这两张表的表结构不完全相同,同步时会用类似nifi这种etl工具做转换。由于是线上系统,不可随意对原有的表结构进行变更。由于是老旧系统,业务繁杂,短时间内无法理清代码逻辑,也就不能从代码层做处理。

用nifi做数据同步时,发现特别是数据删除这种场景,会需要全表扫描做对比,得到被删掉的项,而后在另外一张表中做删除动作,这种方法性能极差。

于是,想到在数据库层面利用触发器记录变化的数据的id,同步时针对id进行同步,可大大提高性能。

二、触发器文档

基本语法

CREATE
[DEFINER = user]
TRIGGER trigger_name
trigger_time trigger_event
ON tbl_name FOR EACH ROW
[trigger_order]
trigger_body
trigger_time: { BEFORE | AFTER }
trigger_event: { INSERT | UPDATE | DELETE }
trigger_order: { FOLLOWS | PRECEDES } other_trigger_name

触发时间有BEFORE和AFTER两种
触发事件,涵盖 增改删 三类,drop table,truncate table 不会激活触发器

参考文档

mysql官方文档 触发器语法和示例
mysql官方文档 创建触发器

mysql非官方中文文档 触发器语法和示例
mysql非官方中文文档 创建触发器

注意点

  • Cascaded foreign key actions do not activate triggers.

  • Within the trigger body, the OLD and NEW keywords enable you to access columns in the rows affected by a trigger. OLD and NEW are MySQL extensions to triggers; they are not case-sensitive.
    In a DELETE trigger, only OLD.col_name can be used; there is no new row.
    In an UPDATE trigger, you can use OLD.col_name to refer to the columns of a row before it is updated and NEW.col_name to refer to the columns of the row after it is updated.
    In an INSERT trigger, only NEW.col_name can be used; there is no old row.

  • 触发器执行失败导致的事务回滚

三、准备

测试版本 mysql 5.6.51

DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(128) CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL COMMENT '名称',
  `remark` varchar(512) CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL COMMENT '备注',
  `created_at` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '创建时间',
  `updated_at` timestamp(0) NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '修改时间',
  `deleted_at` timestamp(0) NULL DEFAULT NULL COMMENT '删除时间',
  PRIMARY KEY (`id`) USING BTREE
) ;

DROP TABLE IF EXISTS `t_user_log`;
CREATE TABLE `t_user_log`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NOT NULL COMMENT 't_user表id',
  `type` varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '更新类型,insert or update or delete',
  `create_at` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '创建时间',
  PRIMARY KEY (`id`) USING BTREE
) ;

四、INSERT 触发器

在t_user表中建立insert触发器,当往t_user表中插入数据时,同时往t_user_log表插入一条数据。

可通过’new.列名’获取插入行的数据

DELIMITER $$
CREATE TRIGGER tr_t_user_insert AFTER INSERT ON t_user FOR EACH ROW
BEGIN
    insert into t_user_log(user_id, type) values(new.id, 'insert');
END $$
DELIMITER ;

插入数据

INSERT INTO t_user(name, remark) VALUES('name1','remark1'), ('name2','remark2'), ('name3','remark3');

执行后在t_user_log表中会出现触发器插入的三条记录。

五、UPDATE 触发器

在t_user表中建立update触发器,当更新t_user表中数据时,同时往t_user_log表插入一条数据。

可通过’new.列名’获取更新的新数据,通过’old.列名’获取更新前的老数据

DELIMITER $$
CREATE TRIGGER tr_t_user_update AFTER UPDATE ON t_user FOR EACH ROW
BEGIN
    IF new.updated_at <> old.updated_at THEN
      insert into t_user_log(user_id, type) values(new.id, 'update');
    ELSE
      insert into t_user_log(user_id, type) values(new.id, 'manual update');
    END IF;
END $$
DELIMITER ;

更新数据


update t_user set remark='remark11' where name='name1';
update t_user set remark='remark22', updated_at=now() where name='name2';

执行后在t_user_log表中会出现触发器插入的2条记录。

六、DELETE 触发器

在t_user表中建立delete触发器,当删除t_user表中数据时,同时往t_user_log表插入一条数据。

通过’old.列名’获取删除的数据

DELIMITER $$
CREATE TRIGGER tr_t_user_delete AFTER DELETE ON t_user FOR EACH ROW
BEGIN
  insert into t_user_log(user_id, type) values(old.id, 'delete');
END $$
DELIMITER ;

删除数据

delete from t_user where name='name1';

执行后在t_user_log表中会出现触发器插入的1条记录。

七、后话

本方法在《高性能MYSQL》一书的触发器一节有提及。虽然在开发过程中,已有的开发经验是应尽量避免甚至不使用触发器。但是在某些特定的场景,诸如本文描述的场景问题,使用触发器记录变更日志,加上触发器本身并不会改动业务逻辑,个人认为可以考虑临时使用触发器解决问题。当然,本文的场景,还有另外一种方法是,使用 canal + binlog 的方式同步数据。

你可能感兴趣的:(mysql,数据库,database,触发器,etl)