在数据库事务中如何保证数据一致性这也是给开发者带来许多的挑战,我们将现实生活中的东西抽象成数据库的关系模型,存放在数据表中,但是现实生活中某些东西有一些约束的,例如付款不能出现负数,人民币最大的面值是 100 元不可能出现比 100 面值还要大的面值,那么这些如何在数据库中保证这些约束?在做某件事情之前查看是否满足某个条件,这就是本篇文章笔者要写的数据库触发器,如何使用 Trigger 来保证数据正确性。
应用场景
例如下面我用 DDL 写一段 SQL 语句创建表结构,在这段 DDL 语句中最后有一句 CHECK (balance >= 0)
,如下:
CREATE TBALE account_info {
uid INT NOT NULL AUTO_INCREMENT,
name VARCHAR(10),
balance INT,
PRIMARY KEY(id),
CHECK (balance >= 0),
}
这是用SQL语句里面的 CHECK 关键字来规定 balance
列的值不能小于 0 的约束,这就是保证现实世界银行账户不可能出现负数情况,但是这个 CHECK 关键字在 MySQL 中是不起作用的插入数据值时不会检测条件是否成立,而在 Oracle 和 SQLServer 中是可以起到作用的,如果某个用 CHECK 语句来判断条件的列不满足条件就会出现错误。
触发器
像是上面的这种场景在日常业务需求经常遇到这些问题,例如一个用户在注册系统的时候会查看当前用户名是否占用?如果被使用了就不允许使用,这时大部分做法是通过业务层的代码进行检查的,通过业务层通过 SELECT 语句条件判断,但是这种情况会出现一个问题如果系统并发量很多如何保证多个用户输入一个相同的用户名情况下还能被正常满足数据正确性,也就是多个用户的条件查询如何保证?一般操作将几个语句包装成一个事务去操作。
上面这种只是一个场景在某个操作之前先要查看是否能满足某个条件才能操作,这种场景很多,在数据库里面可以通过触发器去做,触发器是数据库中提供一个非常实用的功能,它可以在操作者对表进行「增删改」 之前(或之后)被触发,自动执行一段事先写好的 SQL 代码,目前触发器支持的模式有:
名称 | 作用域 |
---|---|
BEFORE INSERT | 在插入数据前检查某个条件,如不满足返回错误信息。 |
AFTER INSERT | 在插入数据之后执行某个预定义好的SQL语句。 |
BEFORE UPDATE | 在更新某个条记录之前检查某个条件,如果不满足返回错误信息。 |
AFTER UDPATE | 在更新某条记录之后执行某个预定义SQL语句,例如记录日志信息。 |
BEFORE DELETE | 在删除数据前,检查是否有关联数据,如有,停止删除操作。 |
AFTER DLELTE | 删除表 A 信息后,自动删除表 B 中与表 A 相关联的信息。 |
有了触发器在某些场景下就不需要再业务层做额外的处理了,有时候也不需要让业务方知道这些事情,例如每条更新操作都会被记录到日志表中,触发器也有自己的特定的语法的,类似于 Linux 下的 Shell 脚本,触发器的基本语法如下:
DELIMITER //
CREATE TRIGGER [触发器的名字]
[触发器执行时机] [触发器监测的对象]
ON [表名]
FOR EACH ROW [触发器主体代码]//
DELIMITER ;
在头部和结尾的 //
符号表示触发器开始与结束,下面的中括号里面都已经其作用,触发器执行时机可以是 BEFORE
和 AFTER
,而触发器监测的对象则为具体对应操作动作类型可以是 INSERT
、 UPDATE
、 DELETE
这些,表名则为具体监测对象表,触发器主体代码则为具体触发器代码,下面是一个实例:
DELIMITER //
CREATE TRIGGER validate_sales_amount
BEFORE INSERT
ON account_info
FOR EACH ROW
IF NEW.balance<=0 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = "你输入Balance金额不合法!";
END IF//
DELIMITER ;
上面这段代码中,我们使用 IF...THEN...END IF
来创建一个监测 INSERT
语句写入的值是否在限定的范围内的触发器,这个触发器的作用就是为了检测在向 account_info
表添加新数据时会检测 balance
列的值是否合法,从而达到前面类似于 CHECK
的效果。
触发器缺点
触发器不是没有缺点的,虽然能解决一部分业务场景下的问题,但是需要注意合理使用。例如一些变态的需求,假设一个系统有张用户表里面存储用户账户和余额信息,需求要让每个用户在完成每笔交易之后都会对其让整个所有用户余额减去用户支出金额,然后看整体系统的金额是否相同,以此来检查整体系统金额完整性。这个需求就不适合触发器,因为每笔交易都要对所有的数据记录进行操作,如果数据量过大,会导致整体每笔交易异常缓慢,所以要分业务场景合理使用触发器。