Bombax's Knowledge Document Notes Bombax's Knowledge Document Notes
首页
  • 前置

    • 尚硅谷Java学习
    • 基础软件安装与配置
  • 核心

    • Java从入门到精通(JDK17版)
    • MySQL从入门到高级-基础篇
    • MySQL从入门到高级-高级篇
    • JDBC 核心技术(JDK21版)
    • JavaWeb 技术
  • 学习笔记

    • POJO 概念
  • Spring Cloud

    • SpringCloud
    • SpringCloud-Alibaba
  • 持久层框架

    • MyBatis
    • MyBatis-Plus
  • 相关知识

    • Mybatis 代码生成工具比较
  • 安全框架

    • 安全框架之 Spring Security
    • 安全框架之 Shiro
  • 定时任务框架

    • 定时任务框架之 Quartz
    • 定时任务框架之 XXL-JOB
  • Java 日志热门框架
  • Git 常用命令
  • Swagger API 文档生成工具
  • Motan RPC (opens new window)
  • Lombok Tutorial (opens new window)
  • Lombok Features (opens new window)
  • FastJSON2 (opens new window)
  • Spring Framework 5 中文文档 (opens new window)
  • XStream (opens new window)
  • fluent-validator 业务逻辑验证框架 (opens new window)
  • ehcache java 缓存框架 (opens new window)
  • jetcache java 缓存框架 (opens new window)
  • caffeine 缓存框架 (opens new window)
  • Spring Cache (opens new window)
  • 主流缓存框架调研 (opens new window)
  • redisson 官方中文文档 (opens new window)
  • LiquiBase 中文学习指南 (opens new window)
  • LiquiBase 官方文档 (opens new window)
  • 分类
  • 归档
GitHub (opens new window)

bombax

小小程序猿
首页
  • 前置

    • 尚硅谷Java学习
    • 基础软件安装与配置
  • 核心

    • Java从入门到精通(JDK17版)
    • MySQL从入门到高级-基础篇
    • MySQL从入门到高级-高级篇
    • JDBC 核心技术(JDK21版)
    • JavaWeb 技术
  • 学习笔记

    • POJO 概念
  • Spring Cloud

    • SpringCloud
    • SpringCloud-Alibaba
  • 持久层框架

    • MyBatis
    • MyBatis-Plus
  • 相关知识

    • Mybatis 代码生成工具比较
  • 安全框架

    • 安全框架之 Spring Security
    • 安全框架之 Shiro
  • 定时任务框架

    • 定时任务框架之 Quartz
    • 定时任务框架之 XXL-JOB
  • Java 日志热门框架
  • Git 常用命令
  • Swagger API 文档生成工具
  • Motan RPC (opens new window)
  • Lombok Tutorial (opens new window)
  • Lombok Features (opens new window)
  • FastJSON2 (opens new window)
  • Spring Framework 5 中文文档 (opens new window)
  • XStream (opens new window)
  • fluent-validator 业务逻辑验证框架 (opens new window)
  • ehcache java 缓存框架 (opens new window)
  • jetcache java 缓存框架 (opens new window)
  • caffeine 缓存框架 (opens new window)
  • Spring Cache (opens new window)
  • 主流缓存框架调研 (opens new window)
  • redisson 官方中文文档 (opens new window)
  • LiquiBase 中文学习指南 (opens new window)
  • LiquiBase 官方文档 (opens new window)
  • 分类
  • 归档
GitHub (opens new window)
  • 前置

  • 核心

    • Java从入门到精通(JDK17版)

    • MySQL从入门到高级-基础篇

      • 第00章_写在前面
      • 第01章_数据库概述
      • 第02章_MySQL 环境搭建
      • 第03章_基本的 SELECT 语句
      • 第04章_运算符
      • 第05章_排序与分页
      • 第06章_多表查询
      • 第07章_单行函数
      • 第08章_聚合函数
      • 第09章_子查询
      • 第10章_创建和管理表
      • 第11章_数据处理之增删改
      • 第12章_MySQL 数据类型精讲
      • 第13章_约束
      • 第14章_视图
      • 第15章_存储过程与函数
      • 第16章_变量、流程控制与游标
      • 第17章_触发器
        • 1. 触发器概述
        • 2. 触发器的创建
          • 2.1 创建触发器语法
          • 2.2 代码举例
        • 3. 查看、删除触发器
          • 3.1 查看触发器
          • 3.2 删除触发器
        • 4. 触发器的优缺点
          • 4.1 优点
          • 4.2 缺点
          • 4.3 注意点
        • 课堂笔记 SQL
        • 课后练习 SQL
      • 第18章_MySQL8 其它新特性
    • MySQL从入门到高级-高级篇

    • JDBC 核心技术(JDK21版)
    • JavaWeb技术

  • 学习笔记

  • Java基础
  • 核心
  • MySQL从入门到高级-基础篇
bombax
2025-01-21
目录

第17章_触发器

# 第 17 章_触发器

讲师:尚硅谷-宋红康(江湖人称:康师傅)

官网:http://www.atguigu.com (opens new window)


在实际开发中,我们经常会遇到这样的情况:有 2 个或者多个相互关联的表,如商品信息和库存信息分别存放在 2 个不同的数据表中,我们在添加一条新商品记录的时候,为了保证数据的完整性,必须同时在库存表中添加一条库存记录。

这样一来,我们就必须把这两个关联的操作步骤写到程序里面,而且要用事务包裹起来,确保这两个操作成为一个原子操作,要么全部执行,要么全部不执行。要是遇到特殊情况,可能还需要对数据进行手动维护,这样就很容易忘记其中的一步,导致数据缺失。

这个时候,咱们可以使用触发器。你可以创建一个触发器,让商品信息数据的插入操作自动触发库存数据的插入操作。这样一来,就不用担心因为忘记添加库存数据而导致的数据缺失了。

# 1. 触发器概述

MySQL 从5.0.2版本开始支持触发器。MySQL 的触发器和存储过程一样,都是嵌入到 MySQL 服务器的一段程序。

触发器是由事件来触发某个操作,这些事件包括INSERT、UPDATE、DELETE事件。所谓事件就是指用户的动作或者触发某项行为。如果定义了触发程序,当数据库执行这些语句时候,就相当于事件发生了,就会自动激发触发器执行相应的操作。

当对数据表中的数据执行插入、更新和删除操作,需要自动执行一些数据库逻辑时,可以使用触发器来实现。

# 2. 触发器的创建

# 2.1 创建触发器语法

创建触发器的语法结构是:

CREATE TRIGGER 触发器名称 
{BEFORE|AFTER} {INSERT|UPDATE|DELETE} ON 表名 
FOR EACH ROW 
触发器执行的语句块;
1
2
3
4

说明:

  • 表名:表示触发器监控的对象。

  • BEFORE|AFTER:表示触发的时间。BEFORE 表示在事件之前触发;AFTER 表示在事件之后触发。

  • INSERT|UPDATE|DELETE:表示触发的事件。

    • INSERT 表示插入记录时触发;
    • UPDATE 表示更新记录时触发;
    • DELETE 表示删除记录时触发。
  • 触发器执行的语句块:可以是单条 SQL 语句,也可以是由 BEGIN…END 结构组成的复合语句块。

# 2.2 代码举例

举例 1:

  1. 创建数据表:

    CREATE TABLE test_trigger (
    id INT PRIMARY KEY AUTO_INCREMENT,
    t_note VARCHAR(30)
    );
    
    
    CREATE TABLE test_trigger_log (
    id INT PRIMARY KEY AUTO_INCREMENT,
    t_log VARCHAR(30)
    );
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
  2. 创建触发器:创建名称为 before_insert 的触发器,向 test_trigger 数据表插入数据之前,向 test_trigger_log 数据表中插入 before_insert 的日志信息。

    DELIMITER //
    
    CREATE TRIGGER before_insert
    BEFORE INSERT ON test_trigger 
    FOR EACH ROW
    BEGIN
        INSERT INTO test_trigger_log (t_log) VALUES('before_insert');
    END //
    
    DELIMITER ;
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
  3. 向 test_trigger 数据表中插入数据

    INSERT INTO test_trigger (t_note) VALUES ('测试 BEFORE INSERT 触发器');
    
    1
  4. 查看 test_trigger_log 数据表中的数据

    mysql> SELECT * FROM test_trigger_log;
    +----+---------------+
    | id | t_log         |
    +----+---------------+
    |  1 | before_insert |
    +----+---------------+
    1 row in set (0.00 sec)
    
    1
    2
    3
    4
    5
    6
    7

举例 2:

  1. 创建名称为 after_insert 的触发器,向 test_trigger 数据表插入数据之后,向 test_trigger_log 数据表中插入 after_insert 的日志信息。

    DELIMITER //
    
    CREATE TRIGGER after_insert
    AFTER INSERT ON test_trigger
    FOR EACH ROW
    BEGIN
        INSERT INTO test_trigger_log (t_log) VALUES('after_insert');
    END //
    
    DELIMITER ;
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
  2. 向 test_trigger 数据表中插入数据。

    INSERT INTO test_trigger (t_note) VALUES ('测试 AFTER INSERT 触发器');
    
    1
  3. 查看 test_trigger_log 数据表中的数据

    mysql> SELECT * FROM test_trigger_log;
    +----+---------------+
    | id | t_log         |
    +----+---------------+
    |  1 | before_insert |
    |  2 | before_insert |
    |  3 | after_insert  |
    +----+---------------+
    3 rows in set (0.00 sec)
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

举例 3:定义触发器“salary_check_trigger”,基于员工表“employees”的 INSERT 事件,在 INSERT 之前检查将要添加的新员工薪资是否大于他领导的薪资,如果大于领导薪资,则报 sqlstate_value 为'HY000'的错误,从而使得添加失败。

DELIMITER //

CREATE TRIGGER salary_check_trigger
BEFORE INSERT ON employees FOR EACH ROW
BEGIN
	DECLARE mgrsalary DOUBLE;
	SELECT salary INTO mgrsalary FROM employees WHERE employee_id = NEW.manager_id;

	IF NEW.salary > mgrsalary THEN
		SIGNAL SQLSTATE 'HY000' SET MESSAGE_TEXT = '薪资高于领导薪资错误';
	END IF;
END //

DELIMITER ;
1
2
3
4
5
6
7
8
9
10
11
12
13
14

上面触发器声明过程中的 NEW 关键字代表 INSERT 添加语句的新记录。

# 3. 查看、删除触发器

# 3.1 查看触发器

查看触发器是查看数据库中已经存在的触发器的定义、状态和语法信息等。

方式 1:查看当前数据库的所有触发器的定义

SHOW TRIGGERS\G
1

方式 2:查看当前数据库中某个触发器的定义

SHOW CREATE TRIGGER 触发器名
1

方式 3:从系统库 information_schema 的 TRIGGERS 表中查询“salary_check_trigger”触发器的信息。

SELECT * FROM information_schema.TRIGGERS;
1

# 3.2 删除触发器

触发器也是数据库对象,删除触发器也用 DROP 语句,语法格式如下:

DROP TRIGGER IF EXISTS 触发器名称;
1

# 4. 触发器的优缺点

# 4.1 优点

1. 触发器可以确保数据的完整性。

假设我们用进货单头表(demo.importhead)来保存进货单的总体信息,包括进货单编号、供货商编号、仓库编号、总计进货数量、总计进货金额和验收日期。

image-20211010233336012

用进货单明细表(demo.importdetails)来保存进货商品的明细,包括进货单编号、商品编号、进货数量、进货价格和进货金额。

image-20211010233344125

每当我们录入、删除和修改一条进货单明细数据的时候,进货单明细表里的数据就会发生变动。这个时候,在进货单头表中的总计数量和总计金额就必须重新计算,否则,进货单头表中的总计数量和总计金额就不等于进货单明细表中数量合计和金额合计了,这就是数据不一致。

为了解决这个问题,我们就可以使用触发器,规定每当进货单明细表有数据插入、修改和删除的操作时,自动触发 2 步操作:

1)重新计算进货单明细表中的数量合计和金额合计;

2)用第一步中计算出来的值更新进货单头表中的合计数量与合计金额。

这样一来,进货单头表中的合计数量与合计金额的值,就始终与进货单明细表中计算出来的合计数量与合计金额的值相同,数据就是一致的,不会互相矛盾。

2. 触发器可以帮助我们记录操作日志。

利用触发器,可以具体记录什么时间发生了什么。比如,记录修改会员储值金额的触发器,就是一个很好的例子。这对我们还原操作执行时的具体场景,更好地定位问题原因很有帮助。

3. 触发器还可以用在操作数据前,对数据进行合法性检查。

比如,超市进货的时候,需要库管录入进货价格。但是,人为操作很容易犯错误,比如说在录入数量的时候,把条形码扫进去了;录入金额的时候,看串了行,录入的价格远超售价,导致账面上的巨亏……这些都可以通过触发器,在实际插入或者更新操作之前,对相应的数据进行检查,及时提示错误,防止错误数据进入系统。

# 4.2 缺点

1. 触发器最大的一个问题就是可读性差。

因为触发器存储在数据库中,并且由事件驱动,这就意味着触发器有可能不受应用层的控制。这对系统维护是非常有挑战的。

比如,创建触发器用于修改会员储值操作。如果触发器中的操作出了问题,会导致会员储值金额更新失败。我用下面的代码演示一下:

mysql> update demo.membermaster set memberdeposit=20 where memberid = 2;
ERROR 1054 (42S22): Unknown column 'aa' in 'field list'
1
2

结果显示,系统提示错误,字段“aa”不存在。

这是因为,触发器中的数据插入操作多了一个字段,系统提示错误。可是,如果你不了解这个触发器,很可能会认为是更新语句本身的问题,或者是会员信息表的结构出了问题。说不定你还会给会员信息表添加一个叫“aa”的字段,试图解决这个问题,结果只能是白费力。

2. 相关数据的变更,可能会导致触发器出错。

特别是数据表结构的变更,都可能会导致触发器出错,进而影响数据操作的正常运行。这些都会由于触发器本身的隐蔽性,影响到应用中错误原因排查的效率。

# 4.3 注意点

注意,如果在子表中定义了外键约束,并且外键指定了 ON UPDATE/DELETE CASCADE/SET NULL 子句,此时修改父表被引用的键值或删除父表被引用的记录行时,也会引起子表的修改和删除操作,此时基于子表的 UPDATE 和 DELETE 语句定义的触发器并不会被激活。

例如:基于子表员工表(t_employee)的 DELETE 语句定义了触发器 t1,而子表的部门编号(did)字段定义了外键约束引用了父表部门表(t_department)的主键列部门编号(did),并且该外键加了“ON DELETE SET NULL”子句,那么如果此时删除父表部门表(t_department)在子表员工表(t_employee)有匹配记录的部门记录时,会引起子表员工表(t_employee)匹配记录的部门编号(did)修改为 NULL,但是此时不会激活触发器 t1。只有直接对子表员工表(t_employee)执行 DELETE 语句时才会激活触发器 t1。

# 课堂笔记 SQL

#第17章_触发器
#0.准备工作
CREATE DATABASE dbtest17;

USE dbtest17;

#1. 创建触发器
#举例1:
#① 创建数据表
CREATE TABLE test_trigger (
id INT PRIMARY KEY AUTO_INCREMENT,
t_note VARCHAR(30)
);


CREATE TABLE test_trigger_log (
id INT PRIMARY KEY AUTO_INCREMENT,
t_log VARCHAR(30)
);

#② 查看表数据
SELECT * FROM test_trigger;

SELECT * FROM test_trigger_log;

#③ 创建触发器
#创建名称为before_insert_test_tri的触发器,向test_trigger数据表插入数据之前,
#向test_trigger_log数据表中插入before_insert的日志信息。

DELIMITER //

CREATE TRIGGER before_insert_test_tri
BEFORE INSERT ON test_trigger
FOR EACH ROW
BEGIN
	INSERT INTO test_trigger_log(t_log)
	VALUES('before insert...');
END //

DELIMITER ;

#④ 测试
INSERT INTO test_trigger(t_note)
VALUES('Tom...');


SELECT * FROM test_trigger;

SELECT * FROM test_trigger_log;


#举例2:
#创建名称为after_insert_test_tri的触发器,向test_trigger数据表插入数据之后,
#向test_trigger_log数据表中插入after_insert的日志信息。

DELIMITER $

CREATE TRIGGER after_insert_test_tri
AFTER INSERT ON test_trigger
FOR EACH ROW
BEGIN
	INSERT INTO test_trigger_log(t_log)
	VALUES('after insert...');
END $

DELIMITER ;

#测试
INSERT INTO test_trigger(t_note)
VALUES('Jerry2...');

SELECT * FROM test_trigger;

SELECT * FROM test_trigger_log;

#举例3:
#定义触发器“salary_check_trigger”,基于员工表“employees”的INSERT事件,
#在INSERT之前检查将要添加的新员工薪资是否大于他领导的薪资,如果大于领导薪资,
#则报sqlstate_value为'HY000'的错误,从而使得添加失败。

#准备工作
CREATE TABLE employees
AS
SELECT * FROM atguigudb.`employees`;


CREATE TABLE departments
AS
SELECT * FROM atguigudb.`departments`;

DESC employees;

#创建触发器
DELIMITER //

CREATE TRIGGER salary_check_trigger
BEFORE INSERT ON employees
FOR EACH ROW
BEGIN
	#查询到要添加的数据的manager的薪资
	DECLARE mgr_sal DOUBLE;
	
	SELECT salary INTO mgr_sal FROM employees 
	WHERE employee_id = NEW.manager_id;
	
	IF NEW.salary > mgr_sal
		THEN SIGNAL SQLSTATE 'HY000' SET MESSAGE_TEXT = '薪资高于领导薪资错误';
	END IF;
END //

DELIMITER ;

#测试
DESC employees;

#添加成功:依然触发了触发器salary_check_trigger的执行
INSERT INTO employees(employee_id,last_name,email,hire_date,job_id,salary,manager_id)
VALUES(300,'Tom','tom@126.com',CURDATE(),'AD_VP',8000,103);

#添加失败
INSERT INTO employees(employee_id,last_name,email,hire_date,job_id,salary,manager_id)
VALUES(301,'Tom1','tom1@126.com',CURDATE(),'AD_VP',10000,103);

SELECT * FROM employees;


#2. 查看触发器
#① 查看当前数据库的所有触发器的定义
SHOW TRIGGERS;

#② 方式2:查看当前数据库中某个触发器的定义
SHOW CREATE TRIGGER salary_check_trigger;

#③ 方式3:从系统库information_schema的TRIGGERS表中查询“salary_check_trigger”触发器的信息。
SELECT * FROM information_schema.TRIGGERS;


#3. 删除触发器
DROP TRIGGER IF EXISTS after_insert_test_tri;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139

# 课后练习 SQL

#第17章_触发器的课后练习
#练习1:
#0. 准备工作
CREATE DATABASE test17_trigger;

USE test17_trigger;

CREATE TABLE emps
AS
SELECT employee_id,last_name,salary
FROM atguigudb.`employees`;

SELECT * FROM emps;

#1. 复制一张emps表的空表emps_back,只有表结构,不包含任何数据
CREATE TABLE emps_back
AS
SELECT * FROM emps
WHERE 1 = 2;


#2. 查询emps_back表中的数据
SELECT * FROM emps_back;


#3. 创建触发器emps_insert_trigger,每当向emps表中添加一条记录时,同步将这条记录
#添加到emps_back表中

DELIMITER //

CREATE TRIGGER emps_insert_trigger
AFTER INSERT ON emps
FOR EACH ROW
BEGIN
	#将新添加到emps表中的记录添加到emps_back表中 
	INSERT INTO emps_back(employee_id,last_name,salary)
	VALUES(NEW.employee_id,NEW.last_name,NEW.salary);
END //

DELIMITER ;

#show triggers;


#4. 验证触发器是否起作用
SELECT * FROM emps;

SELECT * FROM emps_back;

INSERT INTO emps(employee_id,last_name,salary)
VALUES(301,'Tom1',3600);


#练习2:
#0. 准备工作:使用练习1中的emps表
#1. 复制一张emps表的空表emps_back1,只有表结构,不包含任何数据
CREATE TABLE emps_back1
AS
SELECT * 
FROM emps
WHERE 1 = 2;


#2. 查询emps_back1表中的数据
SELECT * FROM emps_back1;


#3. 创建触发器emps_del_trigger,每当向emps表中删除一条记录时,同步将删除的这条记录添加到emps_back1表中
DELIMITER //

CREATE TRIGGER emps_del_trigger
BEFORE DELETE ON emps
FOR EACH ROW
BEGIN
	#将emps表中删除的记录,添加到emps_back1表中。
	INSERT INTO emps_back1(employee_id,last_name,salary)
	VALUES(OLD.employee_id,OLD.last_name,OLD.salary);
END //

DELIMITER ;

#4. 验证触发器是否起作用
DELETE FROM emps
WHERE employee_id = 101;

DELETE FROM emps;

SELECT * FROM emps;

SELECT * FROM emps_back1;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
上次更新: 2025/05/11, 20:55:52
第16章_变量、流程控制与游标
第18章_MySQL8 其它新特性

← 第16章_变量、流程控制与游标 第18章_MySQL8 其它新特性→

最近更新
01
第九章 前端工程化-下
12-11
02
第八章 前端工程化-中
12-11
03
第七章 前端工程化-上
12-04
更多文章>
Theme by Vdoing | Copyright © 2024-2026 bombax | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式