因为系统升级改造,遇到的问题是,新老两个数据库的两张表需要做数据同步,这两张表的表结构不完全相同,同步时会用类似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
) ;
在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表中会出现触发器插入的三条记录。
在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条记录。
在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 的方式同步数据。