MySQL 8.0 创建触发器
触发器是一种特殊类型的存储过程,它会在特定的表上发生特定事件时自动执行。MySQL 支持在 INSERT、UPDATE 和 DELETE 语句执行前后触发操作。本教程将详细介绍如何创建各种类型的触发器,包括基本语法、示例代码和最佳实践。
触发器的基本概念
触发器是一种与表相关联的数据库对象,当表上发生特定事件(如 INSERT、UPDATE 或 DELETE)时,触发器会自动执行。触发器可以用于数据验证、数据审计、维护派生数据、实现复杂的业务规则等场景。
MySQL 支持以下类型的触发器:
- BEFORE INSERT:在插入数据之前触发
- AFTER INSERT:在插入数据之后触发
- BEFORE UPDATE:在更新数据之前触发
- AFTER UPDATE:在更新数据之后触发
- BEFORE DELETE:在删除数据之前触发
- AFTER DELETE:在删除数据之后触发
创建触发器的语法
创建触发器的基本语法如下:
CREATE TRIGGER trigger_name
{BEFORE | AFTER} {INSERT | UPDATE | DELETE}
ON table_name
FOR EACH ROW
BEGIN
-- 触发器执行的语句
END;
其中:
trigger_name
:触发器的名称,在数据库中必须唯一BEFORE | AFTER
:指定触发器是在事件发生前还是发生后执行INSERT | UPDATE | DELETE
:指定触发事件的类型table_name
:触发器关联的表名FOR EACH ROW
:表示触发器对每行数据都执行一次
创建单执行语句的触发器
对于只有一条执行语句的触发器,可以省略 BEGIN 和 END 关键字。下面是一些示例:
创建 BEFORE INSERT 触发器
-- 示例:在插入员工数据前自动设置创建时间和更新时间
delimiter //
CREATE TRIGGER before_employee_insert
BEFORE INSERT ON employees
FOR EACH ROW
SET NEW.created_at = NOW(), NEW.updated_at = NOW();
delimiter ;
创建 AFTER INSERT 触发器
-- 示例:在插入订单后自动更新产品库存
delimiter //
CREATE TRIGGER after_order_insert
AFTER INSERT ON orders
FOR EACH ROW
UPDATE products SET stock_quantity = stock_quantity - NEW.quantity WHERE product_id = NEW.product_id;
delimiter ;
创建 BEFORE UPDATE 触发器
-- 示例:在更新员工数据前自动更新更新时间
delimiter //
CREATE TRIGGER before_employee_update
BEFORE UPDATE ON employees
FOR EACH ROW
SET NEW.updated_at = NOW();
delimiter ;
创建 AFTER UPDATE 触发器
-- 示例:在更新产品价格后记录价格变更历史
delimiter //
CREATE TRIGGER after_product_price_update
AFTER UPDATE ON products
FOR EACH ROW
IF OLD.price <> NEW.price THEN
INSERT INTO product_price_history (product_id, old_price, new_price, changed_at)
VALUES (OLD.product_id, OLD.price, NEW.price, NOW());
END IF;
delimiter ;
创建 BEFORE DELETE 触发器
-- 示例:在删除客户前检查是否有未完成的订单
delimiter //
CREATE TRIGGER before_customer_delete
BEFORE DELETE ON customers
FOR EACH ROW
IF EXISTS (SELECT 1 FROM orders WHERE customer_id = OLD.customer_id AND status = 'PENDING') THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '该客户有未完成的订单,无法删除';
END IF;
delimiter ;
创建 AFTER DELETE 触发器
-- 示例:在删除订单后恢复产品库存
delimiter //
CREATE TRIGGER after_order_delete
AFTER DELETE ON orders
FOR EACH ROW
UPDATE products SET stock_quantity = stock_quantity + OLD.quantity WHERE product_id = OLD.product_id;
delimiter ;
创建有多个执行语句的触发器
对于包含多条执行语句的触发器,需要使用 BEGIN 和 END 关键字,并使用 DELIMITER 语句更改语句分隔符。下面是一些复杂触发器的示例:
创建有多个执行语句的AFTER INSERT触发器
-- 示例1:在插入订单后执行多个后续操作
delimiter //
CREATE TRIGGER after_order_insert_complex
AFTER INSERT ON orders
FOR EACH ROW
begin
-- 更新产品库存
UPDATE products
SET stock_quantity = stock_quantity - NEW.quantity,
updated_at = NOW()
WHERE product_id = NEW.product_id;
-- 创建订单历史记录
INSERT INTO order_history (order_id, status, created_at)
VALUES (NEW.order_id, 'PENDING', NOW());
-- 更新客户的订单统计
UPDATE customers
SET order_count = order_count + 1,
total_spent = total_spent + NEW.total_amount
WHERE customer_id = NEW.customer_id;
-- 更新销售统计
INSERT INTO sales_statistics (product_id, sale_date, quantity_sold, revenue)
VALUES (NEW.product_id, CURDATE(), NEW.quantity, NEW.total_amount)
ON DUPLICATE KEY UPDATE
quantity_sold = quantity_sold + NEW.quantity,
revenue = revenue + NEW.total_amount;
-- 检查库存是否低于预警值
IF (SELECT stock_quantity FROM products WHERE product_id = NEW.product_id) < 10 THEN
INSERT INTO low_stock_alerts (product_id, alert_date)
VALUES (NEW.product_id, NOW());
END IF;
-- 发送订单确认通知
INSERT INTO notifications (user_id, message, created_at, is_read)
VALUES (NEW.customer_id, CONCAT('您的订单 #', NEW.order_id, ' 已创建成功'), NOW(), 0);
end//
delimiter ;
-- 示例2:在插入员工后执行多个后续操作
delimiter //
CREATE TRIGGER after_employee_insert_complex
AFTER INSERT ON employees
FOR EACH ROW
begin
-- 更新部门员工数量
UPDATE departments
SET employee_count = employee_count + 1
WHERE department_id = NEW.department_id;
-- 记录员工创建日志
INSERT INTO employee_logs (employee_id, action, action_time, performed_by)
VALUES (NEW.employee_id, 'CREATE', NOW(), CURRENT_USER());
-- 发送新员工入职通知给部门经理
INSERT INTO notifications (user_id, message, created_at, priority)
SELECT manager_id, CONCAT('新员工 ', NEW.first_name, ' ', NEW.last_name, ' 已加入您的部门'), NOW(), 'MEDIUM'
FROM departments
WHERE department_id = NEW.department_id;
-- 初始化员工培训计划
INSERT INTO employee_training (employee_id, training_name, status, due_date)
VALUES (NEW.employee_id, '入职培训', 'PENDING', DATE_ADD(NOW(), INTERVAL 7 DAY));
end//
delimiter ;
-- 示例3:在插入客户后执行多个后续操作
delimiter //
CREATE TRIGGER after_customer_insert_complex
AFTER INSERT ON customers
FOR EACH ROW
begin
-- 创建客户档案
INSERT INTO customer_profiles (customer_id, created_at)
VALUES (NEW.customer_id, NOW());
-- 创建客户账户
INSERT INTO customer_accounts (customer_id, balance, status)
VALUES (NEW.customer_id, 0.00, 'ACTIVE');
-- 分配客户到默认组
INSERT INTO customer_groups (customer_id, group_id, joined_date)
VALUES (NEW.customer_id, 1, NOW()); -- 1表示普通客户组
-- 记录客户创建日志
INSERT INTO customer_logs (customer_id, action, action_time, performed_by)
VALUES (NEW.customer_id, 'CREATE', NOW(), CURRENT_USER());
-- 发送欢迎邮件通知
INSERT INTO email_queue (recipient_email, subject, body, priority, status)
VALUES (NEW.email, '欢迎加入我们', CONCAT('尊敬的 ', NEW.first_name, ' ', NEW.last_name, ',\n\n欢迎加入我们的服务!'), 'HIGH', 'PENDING');
-- 更新客户统计信息
INSERT INTO customer_statistics (stat_date, new_customers)
VALUES (CURDATE(), 1)
ON DUPLICATE KEY UPDATE
new_customers = new_customers + 1;
end//
delimiter ;
-- 测试示例:插入订单数据,触发器会执行多个后续操作
INSERT INTO orders (customer_id, product_id, quantity, total_amount)
VALUES (101, 501, 2, 199.98);
-- 查看相关表的数据变化,验证触发器是否生效
-- 1. 检查产品库存是否减少
SELECT product_id, stock_quantity FROM products WHERE product_id = 501;
-- 2. 检查订单历史是否有记录
SELECT * FROM order_history WHERE order_id = LAST_INSERT_ID();
-- 3. 检查客户的订单统计是否更新
SELECT customer_id, order_count, total_spent FROM customers WHERE customer_id = 101;
-- 4. 检查销售统计是否更新
SELECT * FROM sales_statistics WHERE product_id = 501 AND sale_date = CURDATE();
-- 5. 检查是否有库存预警(如果库存低于10)
SELECT * FROM low_stock_alerts WHERE product_id = 501;
-- 6. 检查是否有通知发送
SELECT * FROM notifications WHERE user_id = 101 ORDER BY created_at DESC LIMIT 1;
创建有多个执行语句的BEFORE UPDATE触发器
-- 示例1:在更新产品前执行多个验证和处理操作
delimiter //
CREATE TRIGGER before_product_update_complex
BEFORE UPDATE ON products
FOR EACH ROW
begin
-- 验证价格是否合法
IF NEW.price <= 0 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '产品价格必须大于0';
END IF;
-- 验证库存数量是否合法
IF NEW.stock_quantity < 0 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '库存数量不能为负数';
END IF;
-- 记录价格变更历史(如果价格发生变化)
IF OLD.price <> NEW.price THEN
INSERT INTO product_price_history (product_id, old_price, new_price, changed_at, changed_by)
VALUES (OLD.product_id, OLD.price, NEW.price, NOW(), CURRENT_USER());
END IF;
-- 记录库存变更历史(如果库存发生变化)
IF OLD.stock_quantity <> NEW.stock_quantity THEN
INSERT INTO product_inventory_history (product_id, old_quantity, new_quantity, changed_at, changed_by)
VALUES (OLD.product_id, OLD.stock_quantity, NEW.stock_quantity, NOW(), CURRENT_USER());
END IF;
-- 设置产品更新时间和更新人
SET NEW.updated_at = NOW();
SET NEW.updated_by = CURRENT_USER();
-- 如果产品状态变为禁用,标记相关的未处理订单
IF OLD.status = 'ACTIVE' AND NEW.status = 'INACTIVE' THEN
UPDATE orders
SET need_attention = 1,
updated_at = NOW()
WHERE product_id = NEW.product_id
AND status = 'PENDING';
END IF;
end//
delimiter ;
-- 示例2:在更新员工数据前执行多个验证和处理操作
delimiter //
CREATE TRIGGER before_employee_update_complex
BEFORE UPDATE ON employees
FOR EACH ROW
begin
-- 验证年龄是否合法
IF NEW.age < 18 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '员工年龄必须满18岁';
END IF;
-- 验证邮箱格式是否合法
IF NEW.email NOT LIKE '%@%' THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '请输入有效的电子邮箱地址';
END IF;
-- 记录部门变更历史(如果部门发生变化)
IF OLD.department_id <> NEW.department_id THEN
INSERT INTO employee_department_history (employee_id, from_department_id, to_department_id, change_date, changed_by)
VALUES (OLD.employee_id, OLD.department_id, NEW.department_id, NOW(), CURRENT_USER());
-- 更新原部门的员工数量
UPDATE departments
SET employee_count = employee_count - 1
WHERE department_id = OLD.department_id;
-- 更新新部门的员工数量
UPDATE departments
SET employee_count = employee_count + 1
WHERE department_id = NEW.department_id;
END IF;
-- 记录职位变更历史(如果职位发生变化)
IF OLD.position <> NEW.position THEN
INSERT INTO employee_position_history (employee_id, old_position, new_position, change_date, changed_by)
VALUES (OLD.employee_id, OLD.position, NEW.position, NOW(), CURRENT_USER());
END IF;
-- 设置员工更新时间和更新人
SET NEW.updated_at = NOW();
SET NEW.updated_by = CURRENT_USER();
end//
delimiter ;
-- 示例3:在更新订单前执行多个验证和处理操作
delimiter //
CREATE TRIGGER before_order_update_complex
BEFORE UPDATE ON orders
FOR EACH ROW
begin
-- 验证订单状态转换是否合法
IF OLD.status = 'COMPLETED' AND NEW.status <> 'COMPLETED' THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '已完成的订单状态不能修改';
END IF;
IF OLD.status = 'CANCELLED' AND NEW.status <> 'CANCELLED' THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '已取消的订单状态不能修改';
END IF;
-- 记录订单状态变更历史
IF OLD.status <> NEW.status THEN
INSERT INTO order_status_history (order_id, old_status, new_status, changed_at, changed_by)
VALUES (OLD.order_id, OLD.status, NEW.status, NOW(), CURRENT_USER());
-- 如果订单状态变为已完成,设置完成时间
IF NEW.status = 'COMPLETED' THEN
SET NEW.completion_date = NOW();
END IF;
-- 如果订单状态变为已取消,设置取消原因和取消时间
IF NEW.status = 'CANCELLED' AND NEW.cancellation_reason IS NULL THEN
SET NEW.cancellation_reason = '用户取消';
SET NEW.cancellation_date = NOW();
END IF;
END IF;
-- 设置订单更新时间和更新人
SET NEW.updated_at = NOW();
SET NEW.updated_by = CURRENT_USER();
end//
delimiter ;
-- 测试示例:更新产品数据,触发器会执行多个验证和处理操作
-- 成功的情况
UPDATE products
SET price = 129.99,
stock_quantity = 45
WHERE product_id = 1001;
-- 失败的情况(价格不合法)
-- UPDATE products
-- SET price = -10.00
-- WHERE product_id = 1001;
-- 失败的情况(库存数量不合法)
-- UPDATE products
-- SET stock_quantity = -5
-- WHERE product_id = 1001;
-- 查看相关表的数据变化,验证触发器是否生效
-- 1. 检查产品价格变更历史
SELECT * FROM product_price_history WHERE product_id = 1001 ORDER BY changed_at DESC LIMIT 1;
-- 2. 检查产品库存变更历史
SELECT * FROM product_inventory_history WHERE product_id = 1001 ORDER BY changed_at DESC LIMIT 1;
-- 3. 检查产品的更新时间和更新人
SELECT updated_at, updated_by FROM products WHERE product_id = 1001;
创建有多个执行语句的AFTER UPDATE触发器
-- 示例1:在更新订单后执行多个后续操作
delimiter //
CREATE TRIGGER after_order_update_complex
AFTER UPDATE ON orders
FOR EACH ROW
begin
-- 如果订单状态变更为已付款,更新库存和创建发货单
IF OLD.status = 'PENDING' AND NEW.status = 'PAID' THEN
-- 更新产品库存
UPDATE products
SET stock_quantity = stock_quantity - NEW.quantity,
updated_at = NOW()
WHERE product_id = NEW.product_id;
-- 创建发货单
INSERT INTO shipments (order_id, shipment_status, created_at)
VALUES (NEW.order_id, 'PENDING', NOW());
-- 发送订单已付款通知
INSERT INTO notifications (user_id, message, created_at, is_read)
VALUES (NEW.customer_id, CONCAT('您的订单 #', NEW.order_id, ' 已付款,我们将尽快为您发货'), NOW(), 0);
END IF;
-- 如果订单状态变更为已发货,更新发货信息和发送通知
IF OLD.status = 'PAID' AND NEW.status = 'SHIPPED' THEN
-- 更新发货单状态
UPDATE shipments
SET shipment_status = 'SHIPPED',
shipped_date = NOW()
WHERE order_id = NEW.order_id;
-- 发送订单已发货通知
INSERT INTO notifications (user_id, message, created_at, is_read)
VALUES (NEW.customer_id, CONCAT('您的订单 #', NEW.order_id, ' 已发货'), NOW(), 0);
END IF;
-- 如果订单状态变更为已完成,更新完成信息和客户统计
IF OLD.status <> 'COMPLETED' AND NEW.status = 'COMPLETED' THEN
-- 更新客户统计信息
UPDATE customers
SET completed_order_count = completed_order_count + 1,
last_completed_order_date = NOW()
WHERE customer_id = NEW.customer_id;
-- 发送订单已完成通知
INSERT INTO notifications (user_id, message, created_at, is_read)
VALUES (NEW.customer_id, CONCAT('您的订单 #', NEW.order_id, ' 已完成,感谢您的购买'), NOW(), 0);
-- 插入客户购买历史
INSERT INTO customer_purchase_history (customer_id, order_id, purchase_date, amount)
VALUES (NEW.customer_id, NEW.order_id, NOW(), NEW.total_amount);
END IF;
-- 如果订单被取消,恢复库存和发送通知
IF OLD.status <> 'CANCELLED' AND NEW.status = 'CANCELLED' THEN
-- 恢复产品库存
UPDATE products
SET stock_quantity = stock_quantity + NEW.quantity,
updated_at = NOW()
WHERE product_id = NEW.product_id;
-- 发送订单已取消通知
INSERT INTO notifications (user_id, message, created_at, is_read)
VALUES (NEW.customer_id, CONCAT('您的订单 #', NEW.order_id, ' 已取消'), NOW(), 0);
END IF;
-- 记录订单更新日志
INSERT INTO order_update_logs (order_id, updated_fields, updated_at, updated_by)
VALUES (NEW.order_id, 'status', NOW(), CURRENT_USER());
end//
delimiter ;
-- 示例2:在更新客户数据后执行多个后续操作
delimiter //
CREATE TRIGGER after_customer_update_complex
AFTER UPDATE ON customers
FOR EACH ROW
begin
-- 如果客户等级发生变化,记录等级变更历史
IF OLD.customer_level <> NEW.customer_level THEN
INSERT INTO customer_level_history (customer_id, old_level, new_level, change_date, changed_by)
VALUES (NEW.customer_id, OLD.customer_level, NEW.customer_level, NOW(), CURRENT_USER());
-- 发送等级变更通知
INSERT INTO notifications (user_id, message, created_at, is_read)
VALUES (NEW.customer_id, CONCAT('恭喜!您的会员等级已升级为', NEW.customer_level), NOW(), 0);
END IF;
-- 如果客户状态变为禁用,处理相关业务
IF OLD.status = 'ACTIVE' AND NEW.status = 'INACTIVE' THEN
-- 取消所有未处理的订单
UPDATE orders
SET status = 'CANCELLED',
cancellation_reason = '客户账户已禁用',
updated_at = NOW()
WHERE customer_id = NEW.customer_id
AND status IN ('PENDING', 'PROCESSING');
-- 暂停所有订阅服务
UPDATE subscriptions
SET status = 'PAUSED',
pause_reason = '客户账户已禁用',
updated_at = NOW()
WHERE customer_id = NEW.customer_id
AND status = 'ACTIVE';
-- 记录账户禁用日志
INSERT INTO account_disable_logs (user_id, disable_reason, disabled_at, disabled_by)
VALUES (NEW.customer_id, NEW.inactive_reason, NOW(), CURRENT_USER());
END IF;
-- 如果客户状态变为激活,处理相关业务
IF OLD.status = 'INACTIVE' AND NEW.status = 'ACTIVE' THEN
-- 恢复暂停的订阅服务
UPDATE subscriptions
SET status = 'ACTIVE',
updated_at = NOW()
WHERE customer_id = NEW.customer_id
AND status = 'PAUSED'
AND pause_reason = '客户账户已禁用';
-- 记录账户激活日志
INSERT INTO account_activate_logs (user_id, activated_at, activated_by)
VALUES (NEW.customer_id, NOW(), CURRENT_USER());
END IF;
-- 同步更新客户档案信息
UPDATE customer_profiles
SET last_name = NEW.last_name,
first_name = NEW.first_name,
email = NEW.email,
phone = NEW.phone,
updated_at = NOW()
WHERE customer_id = NEW.customer_id;
-- 记录客户更新日志
INSERT INTO customer_update_logs (customer_id, updated_at, updated_by)
VALUES (NEW.customer_id, NOW(), CURRENT_USER());
end//
delimiter ;
-- 示例3:在更新库存后执行多个后续操作
delimiter //
CREATE TRIGGER after_inventory_update_complex
AFTER UPDATE ON inventory
FOR EACH ROW
begin
-- 如果库存低于阈值,创建库存预警
IF NEW.quantity < 10 AND OLD.quantity >= 10 THEN
INSERT INTO inventory_alerts (product_id, current_quantity, alert_type, created_at)
VALUES (NEW.product_id, NEW.quantity, 'LOW_STOCK', NOW());
-- 发送库存预警通知给仓库管理员
INSERT INTO notifications (user_id, message, created_at, priority, is_read)
SELECT user_id, CONCAT('产品 ', (SELECT product_name FROM products WHERE product_id = NEW.product_id), ' 库存不足(当前库存:', NEW.quantity, ')'), NOW(), 'HIGH', 0
FROM users
WHERE user_role = 'WAREHOUSE_MANAGER';
END IF;
-- 如果库存从低于阈值恢复到正常水平,关闭库存预警
IF NEW.quantity >= 10 AND OLD.quantity < 10 THEN
UPDATE inventory_alerts
SET status = 'RESOLVED',
resolved_at = NOW()
WHERE product_id = NEW.product_id
AND alert_type = 'LOW_STOCK'
AND status = 'ACTIVE';
END IF;
-- 记录库存变更历史
INSERT INTO inventory_history (product_id, old_quantity, new_quantity, change_type, changed_at, changed_by)
VALUES (NEW.product_id, OLD.quantity, NEW.quantity, 'UPDATE', NOW(), CURRENT_USER());
-- 更新产品统计信息
UPDATE product_statistics
SET current_stock = NEW.quantity,
last_inventory_update = NOW()
WHERE product_id = NEW.product_id;
-- 如果库存发生大幅变化(超过50%),发送特殊通知
IF ABS(NEW.quantity - OLD.quantity) > OLD.quantity * 0.5 THEN
INSERT INTO admin_notifications (message, created_at, priority)
VALUES (CONCAT('产品 ', (SELECT product_name FROM products WHERE product_id = NEW.product_id), ' 库存发生大幅变化:从 ', OLD.quantity, ' 变为 ', NEW.quantity), NOW(), 'MEDIUM');
END IF;
end//
delimiter ;
-- 测试示例:更新订单状态为已付款,触发器会执行多个后续操作
UPDATE orders
SET status = 'PAID'
WHERE order_id = 2001;
-- 查看相关表的数据变化,验证触发器是否生效
-- 1. 检查产品库存是否减少
SELECT product_id, stock_quantity FROM products WHERE product_id = (SELECT product_id FROM orders WHERE order_id = 2001);
-- 2. 检查是否创建了发货单
SELECT * FROM shipments WHERE order_id = 2001;
-- 3. 检查是否发送了通知
SELECT * FROM notifications WHERE user_id = (SELECT customer_id FROM orders WHERE order_id = 2001) ORDER BY created_at DESC LIMIT 1;
-- 4. 检查订单更新日志
SELECT * FROM order_update_logs WHERE order_id = 2001 ORDER BY updated_at DESC LIMIT 1;
创建有多个执行语句的BEFORE DELETE触发器
-- 示例1:在删除客户前执行多个验证和清理操作
delimiter //
CREATE TRIGGER before_customer_delete_complex
BEFORE DELETE ON customers
FOR EACH ROW
begin
-- 禁止删除有未完成订单的客户
IF EXISTS (SELECT 1 FROM orders WHERE customer_id = OLD.customer_id AND status NOT IN ('COMPLETED', 'CANCELLED')) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '该客户有未完成的订单,无法删除';
END IF;
-- 禁止删除VIP客户
IF OLD.customer_level = 'VIP' THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'VIP客户不能删除,请联系管理员';
END IF;
-- 创建客户备份记录
INSERT INTO deleted_customers (customer_id, first_name, last_name, email, phone, registration_date, deleted_at, deleted_by)
VALUES (OLD.customer_id, OLD.first_name, OLD.last_name, OLD.email, OLD.phone, OLD.registration_date, NOW(), CURRENT_USER());
-- 备份客户订单历史
INSERT INTO deleted_customer_orders (order_id, customer_id, order_date, total_amount, status, deleted_at)
SELECT order_id, customer_id, order_date, total_amount, status, NOW()
FROM orders
WHERE customer_id = OLD.customer_id;
-- 备份客户账户信息
INSERT INTO deleted_customer_accounts (account_id, customer_id, balance, status, deleted_at)
SELECT account_id, customer_id, balance, status, NOW()
FROM customer_accounts
WHERE customer_id = OLD.customer_id;
-- 记录客户删除日志
INSERT INTO customer_deletion_logs (customer_id, deletion_reason, deleted_at, deleted_by)
VALUES (OLD.customer_id, '用户请求删除', NOW(), CURRENT_USER());
-- 发送客户删除通知给管理员
INSERT INTO admin_notifications (message, created_at, priority)
VALUES (CONCAT('客户 ', OLD.first_name, ' ', OLD.last_name, ' (ID:', OLD.customer_id, ') 已被删除'), NOW(), 'MEDIUM');
end//
delimiter ;
-- 示例2:在删除产品前执行多个验证和清理操作
delimiter //
CREATE TRIGGER before_product_delete_complex
BEFORE DELETE ON products
FOR EACH ROW
begin
-- 禁止删除有库存的产品
IF OLD.stock_quantity > 0 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '该产品仍有库存,无法删除';
END IF;
-- 禁止删除畅销产品
IF (SELECT COUNT(*) FROM order_items WHERE product_id = OLD.product_id) > 100 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '该产品为畅销产品,无法删除';
END IF;
-- 创建产品备份记录
INSERT INTO deleted_products (product_id, product_name, category_id, price, stock_quantity, created_at, deleted_at, deleted_by)
VALUES (OLD.product_id, OLD.product_name, OLD.category_id, OLD.price, OLD.stock_quantity, OLD.created_at, NOW(), CURRENT_USER());
-- 备份产品图片
INSERT INTO deleted_product_images (image_id, product_id, image_url, deleted_at)
SELECT image_id, product_id, image_url, NOW()
FROM product_images
WHERE product_id = OLD.product_id;
-- 备份产品评论
INSERT INTO deleted_product_reviews (review_id, product_id, user_id, rating, comment, review_date, deleted_at)
SELECT review_id, product_id, user_id, rating, comment, review_date, NOW()
FROM product_reviews
WHERE product_id = OLD.product_id;
-- 记录产品删除日志
INSERT INTO product_deletion_logs (product_id, deletion_reason, deleted_at, deleted_by)
VALUES (OLD.product_id, '产品已停产', NOW(), CURRENT_USER());
-- 更新产品类别统计
UPDATE product_categories
SET product_count = product_count - 1
WHERE category_id = OLD.category_id;
end//
delimiter ;
-- 示例3:在删除项目前执行多个验证和清理操作
delimiter //
CREATE TRIGGER before_project_delete_complex
BEFORE DELETE ON projects
FOR EACH ROW
begin
-- 禁止删除进行中的项目
IF OLD.status = 'IN_PROGRESS' THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '进行中的项目无法删除';
END IF;
-- 禁止删除有未完成任务的项目
IF EXISTS (SELECT 1 FROM tasks WHERE project_id = OLD.project_id AND status <> 'COMPLETED') THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '该项目有未完成的任务,无法删除';
END IF;
-- 创建项目备份记录
INSERT INTO deleted_projects (project_id, project_name, description, start_date, end_date, status, created_at, deleted_at, deleted_by)
VALUES (OLD.project_id, OLD.project_name, OLD.description, OLD.start_date, OLD.end_date, OLD.status, OLD.created_at, NOW(), CURRENT_USER());
-- 备份项目成员
INSERT INTO deleted_project_members (project_id, user_id, role, joined_date, deleted_at)
SELECT project_id, user_id, role, joined_date, NOW()
FROM project_members
WHERE project_id = OLD.project_id;
-- 备份项目任务
INSERT INTO deleted_project_tasks (task_id, project_id, task_name, description, status, assignee_id, due_date, deleted_at)
SELECT task_id, project_id, task_name, description, status, assignee_id, due_date, NOW()
FROM tasks
WHERE project_id = OLD.project_id;
-- 记录项目删除日志
INSERT INTO project_deletion_logs (project_id, deletion_reason, deleted_at, deleted_by)
VALUES (OLD.project_id, '项目已取消', NOW(), CURRENT_USER());
-- 发送项目删除通知给项目经理
INSERT INTO notifications (user_id, message, created_at, priority)
SELECT project_manager_id, CONCAT('项目 "', OLD.project_name, '" 已被删除'), NOW(), 'HIGH'
FROM projects
WHERE project_id = OLD.project_id;
end//
delimiter ;
-- 测试示例:尝试删除有未完成订单的客户,触发器会阻止删除
-- DELETE FROM customers WHERE customer_id = 3001;
-- 测试示例:删除没有未完成订单的客户,触发器会执行多个验证和清理操作
-- 首先确保客户没有未完成订单
-- UPDATE orders SET status = 'COMPLETED' WHERE customer_id = 3002;
-- 然后删除客户
-- DELETE FROM customers WHERE customer_id = 3002;
-- 查看相关表的数据变化,验证触发器是否生效
-- 1. 检查是否创建了客户备份记录
-- SELECT * FROM deleted_customers WHERE customer_id = 3002;
-- 2. 检查是否备份了客户订单历史
-- SELECT * FROM deleted_customer_orders WHERE customer_id = 3002;
-- 3. 检查是否备份了客户账户信息
-- SELECT * FROM deleted_customer_accounts WHERE customer_id = 3002;
-- 4. 检查客户删除日志
-- SELECT * FROM customer_deletion_logs WHERE customer_id = 3002;
-- 5. 检查是否发送了管理员通知
-- SELECT * FROM admin_notifications WHERE message LIKE '%3002%' ORDER BY created_at DESC LIMIT 1;
创建有多个执行语句的AFTER DELETE触发器
-- 示例1:在删除订单后执行多个清理和后续操作
delimiter //
CREATE TRIGGER after_order_delete_complex
AFTER DELETE ON orders
FOR EACH ROW
begin
-- 恢复产品库存
UPDATE products
SET stock_quantity = stock_quantity + OLD.quantity,
updated_at = NOW()
WHERE product_id = OLD.product_id;
-- 删除订单相关的订单项
DELETE FROM order_items WHERE order_id = OLD.order_id;
-- 删除订单相关的付款记录
DELETE FROM payments WHERE order_id = OLD.order_id;
-- 删除订单相关的发货记录
DELETE FROM shipments WHERE order_id = OLD.order_id;
-- 更新客户的订单统计信息
UPDATE customers
SET order_count = order_count - 1,
total_spent = total_spent - OLD.total_amount
WHERE customer_id = OLD.customer_id;
-- 从销售统计中减去该订单
UPDATE sales_statistics
SET quantity_sold = quantity_sold - OLD.quantity,
revenue = revenue - OLD.total_amount
WHERE product_id = OLD.product_id
AND sale_date = DATE(OLD.order_date);
-- 记录订单删除日志
INSERT INTO order_deletion_logs (order_id, customer_id, total_amount, deleted_at, deleted_by)
VALUES (OLD.order_id, OLD.customer_id, OLD.total_amount, NOW(), CURRENT_USER());
-- 发送订单删除通知给客户
INSERT INTO notifications (user_id, message, created_at, is_read)
VALUES (OLD.customer_id, CONCAT('您的订单 #', OLD.order_id, ' 已被删除'), NOW(), 0);
end//
delimiter ;
-- 示例2:在删除员工后执行多个清理和后续操作
delimiter //
CREATE TRIGGER after_employee_delete_complex
AFTER DELETE ON employees
FOR EACH ROW
begin
-- 删除员工相关的档案
DELETE FROM employee_profiles WHERE employee_id = OLD.employee_id;
-- 删除员工相关的工资记录
DELETE FROM employee_salary WHERE employee_id = OLD.employee_id;
-- 删除员工相关的培训记录
DELETE FROM employee_training WHERE employee_id = OLD.employee_id;
-- 删除员工相关的考勤记录
DELETE FROM attendance_records WHERE employee_id = OLD.employee_id;
-- 删除员工相关的绩效记录
DELETE FROM performance_reviews WHERE employee_id = OLD.employee_id;
-- 更新部门的员工数量
UPDATE departments
SET employee_count = employee_count - 1
WHERE department_id = OLD.department_id;
-- 记录员工删除日志
INSERT INTO employee_deletion_logs (employee_id, first_name, last_name, department_id, deleted_at, deleted_by)
VALUES (OLD.employee_id, OLD.first_name, OLD.last_name, OLD.department_id, NOW(), CURRENT_USER());
-- 发送员工删除通知给部门经理
INSERT INTO notifications (user_id, message, created_at, priority)
SELECT manager_id, CONCAT('员工 ', OLD.first_name, ' ', OLD.last_name, ' (ID:', OLD.employee_id, ') 已被删除'), NOW(), 'MEDIUM'
FROM departments
WHERE department_id = OLD.department_id;
end//
delimiter ;
-- 示例3:在删除产品后执行多个清理和后续操作
delimiter //
CREATE TRIGGER after_product_delete_complex
AFTER DELETE ON products
FOR EACH ROW
begin
-- 删除产品相关的图片
DELETE FROM product_images WHERE product_id = OLD.product_id;
-- 删除产品相关的评论
DELETE FROM product_reviews WHERE product_id = OLD.product_id;
-- 删除产品相关的标签
DELETE FROM product_tags WHERE product_id = OLD.product_id;
-- 删除产品相关的库存记录
DELETE FROM inventory WHERE product_id = OLD.product_id;
-- 删除产品相关的价格历史
DELETE FROM product_price_history WHERE product_id = OLD.product_id;
-- 更新产品类别的产品数量
UPDATE product_categories
SET product_count = product_count - 1
WHERE category_id = OLD.category_id;
-- 记录产品删除日志
INSERT INTO product_deletion_logs (product_id, product_name, category_id, deleted_at, deleted_by)
VALUES (OLD.product_id, OLD.product_name, OLD.category_id, NOW(), CURRENT_USER());
-- 发送产品删除通知给管理员
INSERT INTO admin_notifications (message, created_at, priority)
VALUES (CONCAT('产品 "', OLD.product_name, '" (ID:', OLD.product_id, ') 已被删除'), NOW(), 'LOW');
end//
delimiter ;
-- 测试示例:删除订单,触发器会执行多个清理和后续操作
-- DELETE FROM orders WHERE order_id = 2001;
-- 查看相关表的数据变化,验证触发器是否生效
-- 1. 检查产品库存是否恢复
-- SELECT product_id, stock_quantity FROM products WHERE product_id = (SELECT product_id FROM deleted_orders WHERE order_id = 2001);
-- 2. 检查订单项是否被删除
-- SELECT COUNT(*) FROM order_items WHERE order_id = 2001;
-- 3. 检查客户的订单统计是否更新
-- SELECT customer_id, order_count, total_spent FROM customers WHERE customer_id = (SELECT customer_id FROM deleted_orders WHERE order_id = 2001);
-- 4. 检查订单删除日志
-- SELECT * FROM order_deletion_logs WHERE order_id = 2001;
-- 5. 检查是否发送了客户通知
-- SELECT * FROM notifications WHERE user_id = (SELECT customer_id FROM deleted_orders WHERE order_id = 2001) ORDER BY created_at DESC LIMIT 1;
分隔符的使用
在MySQL中,默认的语句分隔符是分号(;)。当创建包含多条SQL语句的触发器时,需要使用DELIMITER语句更改分隔符,以便MySQL能够正确解析触发器定义中的多条语句。
通常的做法是在创建触发器前使用DELIMITER语句将分隔符更改为//或$$,然后在触发器定义结束后再将分隔符改回分号(;)。
-- 更改分隔符为//
delimiter //
-- 创建包含多条语句的触发器
CREATE TRIGGER trigger_name
{BEFORE | AFTER} {INSERT | UPDATE | DELETE}
ON table_name
FOR EACH ROW
begin
-- 多条SQL语句
-- 第一条语句;
-- 第二条语句;
-- ...
end//
-- 将分隔符改回;
delimiter ;
创建触发器的最佳实践
以下是创建和使用触发器的一些最佳实践:
- 命名规范:使用有意义的名称,并遵循一致的命名约定,如[BEFORE|AFTER]_[TABLE_NAME]_[INSERT|UPDATE|DELETE]。
- 简洁性:触发器应该尽可能简洁,只包含必要的逻辑。复杂的业务逻辑应该放在存储过程中。
- 事务控制:触发器会成为触发它的语句的事务的一部分,因此不要在触发器中使用COMMIT或ROLLBACK语句。
- 性能考虑:避免在触发器中执行耗时的操作,如大量数据的查询或网络操作。
- 错误处理:使用SIGNAL语句在触发器中抛出有意义的错误信息。
- 可维护性:为触发器添加适当的注释,说明其目的和工作原理。
- 文档化:记录所有触发器的用途、触发条件和执行逻辑,以便其他开发人员理解。
触发器的限制和注意事项
在使用触发器时,需要注意以下限制和事项:
- 每个表最多可以有6个触发器(BEFORE INSERT、AFTER INSERT、BEFORE UPDATE、AFTER UPDATE、BEFORE DELETE、AFTER DELETE)。
- 触发器不能返回结果集。
- 触发器不能使用CALL语句调用存储过程,但可以调用不返回结果集的函数。
- 在MySQL 5.7.2及更高版本中,触发器可以使用SIGNAL语句抛出异常。
- 触发器中的OLD和NEW关键字不能用于VIEW。
- 对于分区表,触发器对每个分区分别执行。
- 如果表被删除,与该表相关的所有触发器也会被自动删除。
实际应用案例
数据审计和日志记录
触发器可以用于自动记录数据变更的历史,以便进行审计和追踪。
-- 创建审计日志表
delimiter //
CREATE TABLE user_audit_log (
log_id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT,
action VARCHAR(20),
old_value TEXT,
new_value TEXT,
changed_by VARCHAR(50),
changed_at TIMESTAMP
);
//
-- 创建触发器记录用户数据变更
delimiter //
CREATE TRIGGER after_user_update_audit
AFTER UPDATE ON users
FOR EACH ROW
begin
INSERT INTO user_audit_log (user_id, action, old_value, new_value, changed_by, changed_at)
VALUES (
NEW.user_id,
'UPDATE',
CONCAT('name=', OLD.name, ', email=', OLD.email, ', role=', OLD.role),
CONCAT('name=', NEW.name, ', email=', NEW.email, ', role=', NEW.role),
CURRENT_USER(),
NOW()
);
end//
delimiter ;
数据完整性和一致性维护
触发器可以用于维护数据的完整性和一致性,特别是在复杂的业务场景中。
-- 创建触发器维护账户余额的一致性
delimiter //
CREATE TRIGGER before_transaction_insert
BEFORE INSERT ON transactions
FOR EACH ROW
begin
-- 检查账户余额是否足够
IF NEW.transaction_type = 'DEBIT' THEN
IF (SELECT balance FROM accounts WHERE account_id = NEW.account_id) < NEW.amount THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '账户余额不足';
END IF;
END IF;
-- 自动更新账户余额
IF NEW.transaction_type = 'DEBIT' THEN
UPDATE accounts SET balance = balance - NEW.amount WHERE account_id = NEW.account_id;
ELSE
UPDATE accounts SET balance = balance + NEW.amount WHERE account_id = NEW.account_id;
END IF;
end//
delimiter ;
自动生成派生数据
触发器可以用于自动计算和生成派生数据,减少应用程序的复杂性。
-- 创建触发器自动计算订单总额
delimiter //
CREATE TRIGGER before_order_insert
BEFORE INSERT ON orders
FOR EACH ROW
begin
-- 计算订单总额
DECLARE total DECIMAL(10, 2);
-- 从订单项表中计算总额
SELECT SUM(quantity * price) INTO total
FROM order_items
WHERE order_id = NEW.order_id;
-- 设置订单总额
SET NEW.total_amount = total;
-- 计算税额(假设税率为10%)
SET NEW.tax_amount = total * 0.10;
-- 计算最终应付金额
SET NEW.grand_total = total + NEW.tax_amount;
end//
delimiter ;
创建触发器的常见问题和解决方案
问题 | 原因 | 解决方案 |
---|---|---|
触发器创建后不执行 | 可能是触发条件不匹配或权限问题 | 检查触发事件类型是否正确,确保用户有执行触发器的权限 |
触发器执行报错 | 触发器中的SQL语句有语法错误或逻辑错误 | 检查触发器中的SQL语句,使用SIGNAL语句提供有意义的错误信息 |
性能问题 | 触发器中包含复杂或耗时的操作 | 简化触发器逻辑,将复杂操作移至存储过程或应用程序中 |
死锁 | 触发器与触发它的语句在同一事务中,可能导致死锁 | 避免在触发器中执行可能导致死锁的操作,如长时间持有锁 |
递归触发 | 触发器修改了另一个表,而该表又有触发器触发当前表的操作 | 重新设计触发器逻辑,避免循环触发,或使用标志字段控制递归 |