当前位置:首页 > 问答 > 正文

用触发器来搞定数据库表改动和日志记录那些事儿,怎么弄比较靠谱

在实际的软件开发和系统维护中,我们经常需要知道数据库里的重要数据是谁、在什么时候、修改了什么,用户修改了个人信息,管理员调整了商品价格,或者某个关键配置被更改了,单纯靠应用程序去记录这些变动,有时候会不靠谱,比如万一程序有bug漏记了,或者有多条代码路径其中一条忘记写日志了,这时候,一个比较靠谱的办法是让数据库自己来“监视”自己,这就是触发器的用武之地。

触发器就像是安插在数据库表里的一个“自动监控程序”或者“小秘书”,你可以给它下命令:当这张表发生插入新数据、更新老数据或者删除数据的时候,你自动去执行一段我预先写好的操作,最典型的应用就是做变更日志记录。

用触发器来搞定数据库表改动和日志记录那些事儿,怎么弄比较靠谱

具体怎么弄才靠谱呢?可以从下面几个方面来考虑。

第一,明确目标,只给必要的表加触发器。 不是所有表都需要这种严密的监控,通常是一些核心的业务数据表,比如用户表、订单表、财务相关的表,它们的变动至关重要,需要留有痕迹,如果给每个表都加上触发器,可能会对数据库的整体性能产生不必要的影响,首先要评估哪些数据的变更是必须被记录的。

用触发器来搞定数据库表改动和日志记录那些事儿,怎么弄比较靠谱

第二,设计好记录日志的“小本本”——也就是日志表。 这个日志表的设计很有讲究,根据开源项目“Ruoyi”的管理系统设计思路,一个通用的操作日志表通常会包含以下几个关键字段(根据开源项目Ruoyi的管理系统设计思路):

  • 日志主键: 每条日志的唯一标识。
  • 操作模块: 说明是哪个功能模块的操作,用户管理”、“订单管理”。
  • 操作类型: 就是具体的动作,通常是INSERT(新增)、UPDATE(更新)、DELETE(删除)这三种之一。
  • 操作人员: 记录是哪个用户执行的操作,这里通常存储用户ID或用户名。
  • 操作时间: 记录操作发生的具体时间戳。
  • 业务主键: 这是非常关键的一环,要记录被修改的那条数据的主键值,这样以后查日志的时候,才能精准定位到是哪条数据的历史变更。
  • 在更新和删除操作时,记录下数据被改之前的样子,通常可以把整条记录的旧值转换成一个JSON字符串存起来。
  • 在新增和更新操作时,记录下数据变更后的新样子,同样可以存成JSON字符串。
  • 操作IP地址: 记录用户是从哪个IP地址发起的操作,有助于安全审计。

第三,精心编写触发器逻辑。 以MySQL为例,创建一个在更新操作后触发的触发器,其核心思路是(基于通用数据库知识):

用触发器来搞定数据库表改动和日志记录那些事儿,怎么弄比较靠谱

  1. UPDATE操作之后(使用AFTER UPDATE)激活触发器。
  2. 使用NEW关键字来获取当前更新操作提交的数据值。
  3. 使用OLD关键字来获取更新之前的旧数据值。
  4. OLDNEW中的关键字段,甚至是整条记录,组合成我们需要的格式(比如JSON)。
  5. 将上述信息,连同当前用户(可以从数据库会话变量中获取,比如@login_user,这个变量需要应用程序在连接数据库后设置)、当前时间等,一起插入到我们事先建好的那个日志表中。

举个例子,假设有一张员工表emp,我们要记录它的更新日志,触发器的伪代码逻辑大概是这样的(基于通用数据库知识):

CREATE TRIGGER trig_emp_after_update
AFTER UPDATE ON emp
FOR EACH ROW
BEGIN
    INSERT INTO sys_oper_log (操作模块, 操作类型, 操作人员, 操作时间, 业务主键, 变更前内容, 变更后内容)
    VALUES (
        '员工管理',
        'UPDATE',
        @current_user_id, -- 假设应用层设置了当前用户ID的会话变量
        NOW(),
        OLD.emp_id, -- 假设emp_id是主键
        JSON_OBJECT('name', OLD.emp_name, 'salary', OLD.salary), -- 将旧数据转为JSON
        JSON_OBJECT('name', NEW.emp_name, 'salary', NEW.salary)  -- 将新数据转为JSON
    );
END;

这样,每当有人更新了emp表里某个员工的姓名或工资,数据库就会自动在sys_oper_log表里记下一笔账,清清楚楚。

第四,考虑性能和异常处理。 触发器虽然方便,但因为它是在数据库事务内部执行的,如果触发器本身写得复杂或者效率低下,会拖慢原本的数据操作速度,因此要注意:(基于通用数据库最佳实践)

  • 日志表最好单独放在一个硬盘上,避免和业务表I/O争抢。
  • 触发器逻辑要尽可能简单高效,避免在触发器里执行复杂的查询或大量的计算。
  • 定期清理或归档旧的日志数据,防止日志表无限膨胀。
  • 要确保触发器不会因为异常(比如日志表空间已满)而导致主业务操作失败,必要时需要做一定的异常捕获和处理。

用触发器搞定数据库表改动和日志记录,靠谱的做法是: 精选需要记录的表,设计一个结构合理的日志表,编写准确高效的触发器逻辑(充分利用OLD和NEW关键字捕获数据变化),并始终关注其对性能的影响和维护成本,这种方法将日志记录的可靠性交给了数据库本身,实现了与应用业务的解耦,保证了记录的真实性和完整性,是进行数据审计和问题追溯的有效手段。