25. 使用MySQL之使用触发器

news/2024/11/14 8:47:48/文章来源:https://www.cnblogs.com/hisun9/p/18542682

1. 触发器

MySQL语句在需要时被执行,存储过程也是如此。但是,如果你想要某条语句(或某些语句)在事件发生时自动执行,怎么办呢?例如:

  • 每当增加一个顾客到某个数据库表时,都检查其电话号码格式是
    否正确,州的缩写是否为大写;

  • 每当订购一个产品时,都从库存数量中减去订购的数量;

  • 无论何时删除一行,都在某个存档表中保留一个副本。

所有这些例子的共同之处是它们都需要在某个表发生更改时自动处理。这确切地说就是触发器。触发器是MySQL响应以下任意语句而自动执行的一条MySQL语句(或位于BEGIN和END语句之间的一组语句):

  • DELETE;

  • INSERT

  • UPDATE

其他MySQL语句不支持触发器。

2. 创建触发器

在创建触发器时,需要给出4条信息:

  • 唯一的触发器名;

  • 触发器关联的表;

  • 触发器应该响应的活动(DELETE、INSERT或UPDATE);

  • 触发器何时执行(处理之前或之后)。

补充:

  • 保持每个数据库的触发器名唯一:

    在MySQL 5中,触发器名必须在每个表中唯一,但不是在每个数据库中唯一。这表示同一数据库中的两个表可具有相同名字的触发器。这在其他每个数据库触发器名必须唯一的DBMS中是不允许的,而且以后的MySQL版本很可能会使命名规则更为严格。因此,现在最好是在数据库范围内使用唯一的触发器名。

    可以这样理解上面这段话:

    1. 在MySQL 5中,触发器名在表级范围内是唯一的:也就是说,在同一个表中,触发器的名称不能重复,但不同表之间可以有相同名称的触发器。例如,在数据库 db1 中,你可以在表 table1 和 table2 中分别创建名为 trigger1 的触发器,系统允许这种情况,因为它们作用于不同的表。

    2. 其他DBMS要求触发器在数据库级范围内唯一:在许多其他数据库系统中(例如Oracle、SQL Server等),要求整个数据库范围内触发器名必须唯一,不能在不同表上创建相同名称的触发器。也就是说,一旦在数据库的某个表上创建了名为 trigger1 的触发器,那么在同一个数据库的其他表上不能再创建同名触发器。

触发器用CREATE TRIGGER语句创建。

比如:

create trigger newproduct after insert on products
for each row select 'Product added';

CREATE TRIGGER用来创建名为newproduct的新触发器。触发器可在一个操作发生之前或之后执行,这里给出了AFTER INSERT,所以此触发器将在INSERT语句成功执行后执行。这个触发器还指定FOR EACH ROW,因此代码对每个插入行执行。在这个例子中,文本Product added将对每个插入的行显示一次。

为了测试这个触发器,使用INSERT语句添加一行或多行到products中,你将看到对每个成功的插入,显示Product added消息。

插一句题外话

其实如果按书上的这个语句写的话,是会报错的:Error Code: 1415. Not allowed to return a result set from a trigger

改成这样就不报错了:

create trigger newproduct after insert on products
for each row select 'Product added' into @test;

原因是从MySQL5以后不支持触发器返回结果集,需要用一个变量接受结果集(此处即@test变量)。

向products表插入两条数据后,再使用select @test;查看变量@test

img

输出如下:

img

但是其实上面添加一个变量的方法感觉治标不治本,还有别的更好的方法:

可以使用 INSERT 记录日志信息:

-- 创建日志表
CREATE TABLE product_log (id INT AUTO_INCREMENT PRIMARY KEY,message VARCHAR(255),created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);-- 创建触发器,将日志信息插入到日志表
DELIMITER //
CREATE TRIGGER newproduct AFTER INSERT ON products 
FOR EACH ROW 
BEGININSERT INTO product_log (message) VALUES ('Product added');
END //
DELIMITER ;

这样,每当在 products 表中插入一条新记录,触发器就会自动将“Product added”消息插入到 product_log 表中。

回归正题

注意:

  • 仅支持表:

    只有表才支持触发器,视图不支持(临时表也不支持)。

触发器按每个表每个事件每次地定义,每个表每个事件每次只允许一个触发器。因此,每个表最多支持6个触发器(每条INSERT、UPDATE和DELETE的之前和之后)。单一触发器不能与多个事件或多个表关联,所以,如果你需要一个对INSERT和UPDATE操作执行的触发器,则应该定义两个触发器。

补充:

  • 触发器失败:

    如果BEFORE触发器失败,则MySQL将不执行请求的操作。此外,如果BEFORE触发器或语句本身失败,MySQL将不执行AFTER触发器(如果有的话)。

    插句题外话

    这段话的意思是:在MySQL中,如果 BEFORE 触发器执行失败,那么MySQL会中止该操作,即不再执行实际的数据库操作(例如 INSERT、UPDATE 或 DELETE),而且 AFTER 触发器(如果存在)也不会被触发。这是MySQL的一个重要机制,用于确保数据的完整性和一致性。

    具体解释如下:

    1. BEFORE 触发器失败时:

    BEFORE 触发器是在指定操作(如 INSERT、UPDATE 或 DELETE)之前执行的。如果 BEFORE 触发器遇到错误或执行失败,MySQL会取消该操作,因此不会执行该操作本身。例如,如果一个 INSERT 操作的 BEFORE INSERT 触发器失败,那么这条 INSERT 语句就不会被执行。同时,任何相关的AFTER 触发器也不会被执行,因为 AFTER 触发器只有在请求的操作成功执行后才会被触发。

    1. 语句本身失败时:

    即使 BEFORE 触发器成功执行,但如果实际的数据库操作(例如 INSERT)本身因某种原因失败(例如违反了唯一性约束),那么 AFTER 触发器也不会执行。因为 AFTER 触发器是基于操作成功执行后的情况来触发的。

    1. 举个例子

    假设有一张 products 表,并为 INSERT 操作定义了 BEFORE INSERT 和 AFTER INSERT 触发器。

    DELIMITER //-- BEFORE INSERT 触发器
    CREATE TRIGGER before_insert_product BEFORE INSERT ON products
    FOR EACH ROW
    BEGIN-- 如果某些条件不满足,触发器可以引发错误IF NEW.price <= 0 THENSIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Price must be greater than zero';END IF;
    END//-- AFTER INSERT 触发器
    CREATE TRIGGER after_insert_product AFTER INSERT ON products
    FOR EACH ROW
    BEGIN-- 记录日志或执行其他操作INSERT INTO product_log (message) VALUES ('Product added');
    END//DELIMITER ;
    

    在这个例子中:

    • 如果在 BEFORE INSERT 触发器中发现 price 小于等于 0,那么触发器会引发错误并停止执行,这样 INSERT 操作就不会完成,也不会触发AFTER INSERT 触发器。

    • 只有当 BEFORE INSERT 成功且 INSERT 语句本身没有出错时,AFTER INSERT 触发器才会执行。

    通过这种机制,MySQL确保了只有在所有操作都顺利完成的情况下才会触发 AFTER 触发器,避免在部分失败的操作中执行额外的后续逻辑。

3. 删除触发器

为了删除一个触发器,可使用DROP TRIGGER语句。

比如:

drop trigger newproduct;

触发器不能更新或覆盖。为了修改一个触发器,必须先删除它,然后再重新创建。

4. 使用触发器

在有了前面的基础知识后,我们现在来看所支持的每种触发器类型以及它们的差别。

4.1 INSERT 触发器

INSERT触发器在INSERT语句执行之前或之后执行。需要知道以下几点:

  • 在INSERT触发器代码内,可引用一个名为NEW的虚拟表,访问被插入的行;

  • 在BEFORE INSERT触发器中,NEW中的值也可以被更新(允许更改被插入的值);

  • 对于AUTO_INCREMENT列,NEW在INSERT执行之前包含0,在INSERT执行之后包含新的自动生成值。

下面举一个例子(一个实际有用的例子)。

AUTO_INCREMENT列具有MySQL自动赋予的值。第21章建议了几种确定新生成值的方法,但下面是一种更好的方法:

create trigger neworder after insert on orders
for each row select new.order_num;

插句题外话

如果按上面那样写的话,会报大大的错

img

可以尝试写成这样:

create trigger neworder after insert on orders
for each row select new.order_num into @neworder_num;

为什么会报错呢? 为什么写成这样不报错呢?

  • 在 MySQL5 后,触发器(trigger)不允许返回结果集(即执行 SELECT 语句直接输出数据)。

  • 在 MySQL 中,SELECT ... INTO @变量名SELECT ... 的行为有所不同:

    • SELECT ... INTO @变量名:这种语法将查询结果存储到一个用户定义的变量中,不会返回结果集。这在触发器或存储过程内部是允许的,因为它不会将数据返回到客户端,只是在内部操作。

    • SELECT ...:直接执行 SELECT 查询会返回一个结果集给客户端,触发器内部不允许这样做,因此会报错 1415。

    通过使用 SELECT ... INTO @变量名,可以将值存储在变量中进行后续处理,而不触发返回结果集的限制。这也是为什么加上 INTO 后,代码在触发器中可以正常运行。

  • 想了解一下@变量名的话可以看下这篇博客 MySQL中的@变量名

回归正题

此代码创建一个名为neworder的触发器,它按照AFTER INSERT ON orders执行。在插入一个新订单到orders表时,MySQL生成一个新订单号并保存到order_num中。触发器从NEW.order_num取得这个值并返回它。

此触发器必须按照AFTER INSERT执行,因为在BEFORE INSERT语句执行之前,新order_num还没有生成。

对于orders的每次插入使用这个触发器将总是返回新的订单号。

为测试这个触发器,试着插入一下新行,如下所示:

insert into orders(order_date, cust_id)
values(now(), 10001);

orders 包 含 3 个列。 order_date 和 cust_id 必须给出,order_num由MySQL自动生成,而现在order_num还自动被返回。

img

补充:

  • BEFORE或AFTER?

    通常,将BEFORE用于数据验证和净化(目的是保证插入表中的数据确实是需要的数据)。本提示也适用于UPDATE触发器。

4.2 DELETE 触发器

DELETE触发器在DELETE语句执行之前或之后执行。需要知道以下两点:

  • 在DELETE触发器代码内,可以引用一个名为OLD的虚拟表,访问被删除的行;

  • OLD中的值全都是只读的,不能更新。

下面的例子演示使用OLD保存将要被删除的行到一个存档表中:

CREATE TABLE archive_orders
(order_num  int      NOT NULL AUTO_INCREMENT,order_date datetime NOT NULL ,cust_id    int      NOT NULL ,PRIMARY KEY (order_num)
);delimiter //
create trigger deleteorder before delete on orders
for each row
begininsert into archive_orders(order_num, order_date, cust_id)values(old.order_num, old.order_date, old.cust_id);
end //
delimiter ;

在任意订单被删除前将执行此触发器。它使用一条INSERT语句将OLD中的值(要被删除的订单)保存到一个名为archive_ orders的存档表中(为实际使用这个例子,需要用与orders相同的列创建一个名为archive_orders的表)。

使用BEFORE DELETE触发器的优点(相对于AFTER DELETE触发器来说)为,如果由于某种原因,订单不能存档,DELETE本身将被放弃。

补充:

  • 多语句触发器:

    正如所见,触发器deleteorder使用BEGIN和END语句标记触发器体。这在此例子中并不是必需的,不过也没有害处。使用BEGIN END块的好处是触发器能容纳多条SQL语句(在BEGIN END块中一条挨着一条)。

插句题外话

给出我的实践过程图:

未对orders表执行删除订单操作时,orders表的样子:

img

对orders表执行删除订单操作后,查看archive_ orders表和orders表:

img

img

4.3 UPDATE 触发器

UPDATE触发器在UPDATE语句执行之前或之后执行。需要知道以下几点:

  • 在UPDATE触发器代码中,可以引用一个名为OLD的虚拟表访问以前(UPDATE语句前)的值,引用一个名为NEW的虚拟表访问新更新的值;

  • 在BEFORE UPDATE触发器中,NEW中的值可能也被更新(允许更改将要用于UPDATE语句中的值);

  • OLD中的值全都是只读的,不能更新。

下面的例子保证州名缩写总是大写(不管UPDATE语句中给出的是大写还是小写):

create trigger updatevendor before update on vendors
for each row set new.vend_state = upper(new.vend_state);

显然,任何数据净化都需要在UPDATE语句之前进行,就像这个例子中一样。每次更新一个行时,NEW.vend_state中的值(将用来更新表行的值)都用Upper(NEW.vend_state)替换。

4.4 关于触发器的进一步介绍

我们再介绍一些使用触发器时需要记住的重点。

  • 与其他DBMS相比,MySQL 5中支持的触发器相当初级。未来的MySQL版本中有一些改进和增强触发器支持的计划。

  • 创建触发器可能需要特殊的安全访问权限,但是,触发器的执行是自动的。如果INSERT、UPDATE或DELETE语句能够执行,则相关的触发器也能执行。

  • 应该用触发器来保证数据的一致性(大小写、格式等)。在触发器中执行这种类型的处理的优点是它总是进行这种处理,而且是透明地进行,与客户机应用无关。

  • 触发器的一种非常有意义的使用是创建审计跟踪。使用触发器,把更改(如果需要,甚至还有之前和之后的状态)记录到另一个表非常容易。

  • 遗憾的是,MySQL触发器中不支持CALL语句。这表示不能从触发器内调用存储过程。所需的存储过程代码需要复制到触发器内。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/832479.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Mysql表分区实操

创建分区表 create table user(id int(11) not null,name varchar(32) not null) partition by range(id) ( partition p0 values less than(10), partition p1 values less than(20), partition p2 values less than(30), partition p3 values less than maxvalue )分区表数据…

【Azure Bot Service】部署NodeJS ChatBot代码到App Service中无法自动启动

问题描述 把NodeJS的ChatBot代码部署到App Service环境中,通过VS Code直接部署,显示部署成功。但是通过URL访问时候,却是 :( Application Error 。问题解答 App Service遇见Application Error,第一步,查看日志。 发现启动时候遇见: 2024-11-12T12:22:40.366223350Z Error…

Chrome如何卸载掉插件?如此简单!

前言 我之前由于好奇,一下子装了很多的chrome插件,这些插件虽然都有很多功能,但是,由于我的电脑性能不佳,浏览器一下子装这么多插件,有点带不动了。所以,我就想到了卸载谷歌浏览器,哦不,是卸载浏览器里面的插件。今天,我就来介绍下,如何在chrome里面卸载一些不常用的…

rocky9如何重置root密码

rocky9如何重置root密码 第一步第二步 在箭头指的地方把 "ro" 改成 "rw"另一个箭头指的地方写上如下内容:init=/bin/bash如图所示第三步 Ctrl + x 按当前的配置启动第四步 输入命令root 用户密码直接输入以下内容passwd如果更改其他用户第五步 最为关键的…

多校A层冲刺NOIP2024模拟赛21

多校A层冲刺NOIP2024模拟赛21\(T1\) A. 送信卒 \(90pts/100pts\)部分分\(90pts\)设最后的可能的最短路中左右共移动了 \(d\) 次,上下共移动了 \(x\) 次。则等价于求 \(\min \{ x_{i}k+d_{i} \}=s\) 的解,观察到 \(d \in [0,\min(\left\lceil \frac{nm}{2} \right\rceil,s)]\)…

[论文阅读] ZePo: Zero-Shot Portrait Stylization with Faster Sampling

写在前面 原文:ZePo GitHub:Github ZePo 关键词:肖像风格化、扩散模型、零样本快速生成 阅读理由:对扩散模型的改进,可以实现零样本快速生成图像,学习一下思路以及实验设计 前置知识:LCM以及GithubLCM(找时间写一下),可参考LCM&CM,一致性蒸馏、图像质量评价速览…

2024.11.12 鲜花

P11270 【MX-S5-T4】魔法少女们 题解这世界那么多人 这世界有那么多人 人群里 敞着一扇门 我迷朦的眼睛里长存 初见你蓝色清晨 这世界有那么多人 多幸运 我有个我们 这悠长命运中的晨昏 常让我 望远方出神 灰树叶飘转在池塘 看飞机轰的一声去远乡 光阴的长廊 脚步声叫嚷 灯一亮…

Java代码实现行列转换

本代码想要达到的效果 测试完整代码如下(copy直接运行): public class TestConvert { public static void main(String[] args) { ArrayList<Attribute> sourceList = new ArrayList<>(); for (int i = 0; i < 3; i++) { for (i…

SS241112A. 定向越野(walk)

这道题目要求找到从起点出发遍历所有点并回到起点的最小路径长度,路径必须直角拐弯。文章证明了答案是有理数,并通过直观分析和大胆猜测得出初始方向必须是某一个向量的方向,最终使用状压 DP 解决问题,时间复杂度为 $O(n^4 2^n)$。SS241112A. 定向越野(walk) 题意 给你 \…

第六课 Python之模块

一、模块的介绍 (1)python模块,是一个python文件,以一个.py文件,包含了python对象定义和pyhton语句 (2)python对象定义和python语句 (3)模块让你能够有逻辑地组织你的python代码段。 (4)把相关的代码分配到一个模块里能让你的代码更好用,更易懂 (5)模块能定义函数…

redis集群搭建 - cluster模式

搭建一套redis cluster集群。概述 搭建一套redis cluster集群。架构 192.168.0.21:6379 主 192.168.0.23:6380 从 192.168.0.22:6379 主 192.168.0.21:6380 从 192.168.0.23:6379 主 192.168.0.22:6380 从 修改这三台服务器的host文件(选做) vim /etc/hosts192.168.0.21 node…

推荐一个Elasticsearch ES可视化客户端工具:ES-King

ES-King:开源免费,一个现代、实用的ES GUI客户端,支持多平台。 下载地址:https://github.com/Bronya0/ES-King 功能清单 详尽的集群信息:节点信息、堆内存占用、总内存占用、cpu占用、磁盘占用、网络流量、节点角色、集群健康、5分钟负载、每个节点的字段缓存、段缓存、查…