MySQL触发器与事件调度器自动化实战
Orion K Lv6

MySQL触发器与事件调度器自动化实战

在MySQL数据库管理中,触发器(Triggers)和事件调度器(Event Scheduler)是实现自动化操作的重要工具。本文将通过实战案例详细介绍如何使用这些功能来提高数据库的自动化程度和业务处理效率。

触发器基础与应用

1. 触发器类型和基本语法

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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
-- 查看触发器支持情况
SHOW VARIABLES LIKE 'log_bin_trust_function_creators';

-- 如果需要,设置允许创建触发器
SET GLOBAL log_bin_trust_function_creators = 1;

-- 创建测试表
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
email VARCHAR(100) NOT NULL,
password_hash VARCHAR(255) NOT NULL,
status TINYINT DEFAULT 1,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

CREATE TABLE user_audit_log (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT,
action ENUM('INSERT', 'UPDATE', 'DELETE'),
old_values JSON,
new_values JSON,
changed_by VARCHAR(50),
changed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- BEFORE INSERT 触发器
DELIMITER //
CREATE TRIGGER tr_users_before_insert
BEFORE INSERT ON users
FOR EACH ROW
BEGIN
-- 验证邮箱格式
IF NEW.email NOT REGEXP '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$' THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '邮箱格式不正确';
END IF;

-- 验证用户名长度
IF LENGTH(NEW.username) < 3 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '用户名长度不能少于3个字符';
END IF;

-- 自动生成创建时间
SET NEW.created_at = NOW();
END //
DELIMITER ;

-- AFTER INSERT 触发器
DELIMITER //
CREATE TRIGGER tr_users_after_insert
AFTER INSERT ON users
FOR EACH ROW
BEGIN
-- 记录审计日志
INSERT INTO user_audit_log (user_id, action, new_values, changed_by)
VALUES (
NEW.id,
'INSERT',
JSON_OBJECT(
'username', NEW.username,
'email', NEW.email,
'status', NEW.status
),
USER()
);

-- 创建用户配置文件
INSERT INTO user_profiles (user_id, nickname, created_at)
VALUES (NEW.id, NEW.username, NOW());
END //
DELIMITER ;

-- BEFORE UPDATE 触发器
DELIMITER //
CREATE TRIGGER tr_users_before_update
BEFORE UPDATE ON users
FOR EACH ROW
BEGIN
-- 防止关键字段被修改
IF OLD.id != NEW.id THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '不允许修改用户ID';
END IF;

-- 验证状态值
IF NEW.status NOT IN (0, 1, 2) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '用户状态值无效';
END IF;

-- 自动更新修改时间
SET NEW.updated_at = NOW();
END //
DELIMITER ;

-- AFTER UPDATE 触发器
DELIMITER //
CREATE TRIGGER tr_users_after_update
AFTER UPDATE ON users
FOR EACH ROW
BEGIN
-- 记录变更日志
INSERT INTO user_audit_log (user_id, action, old_values, new_values, changed_by)
VALUES (
NEW.id,
'UPDATE',
JSON_OBJECT(
'username', OLD.username,
'email', OLD.email,
'status', OLD.status
),
JSON_OBJECT(
'username', NEW.username,
'email', NEW.email,
'status', NEW.status
),
USER()
);

-- 如果状态改变,记录状态变更历史
IF OLD.status != NEW.status THEN
INSERT INTO user_status_history (user_id, old_status, new_status, changed_at)
VALUES (NEW.id, OLD.status, NEW.status, NOW());
END IF;
END //
DELIMITER ;

-- BEFORE DELETE 触发器
DELIMITER //
CREATE TRIGGER tr_users_before_delete
BEFORE DELETE ON users
FOR EACH ROW
BEGIN
-- 检查是否有关联数据
DECLARE order_count INT DEFAULT 0;

SELECT COUNT(*) INTO order_count
FROM orders
WHERE user_id = OLD.id;

IF order_count > 0 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '用户有关联订单,不能删除,请先处理相关数据';
END IF;
END //
DELIMITER ;

-- AFTER DELETE 触发器
DELIMITER //
CREATE TRIGGER tr_users_after_delete
AFTER DELETE ON users
FOR EACH ROW
BEGIN
-- 记录删除日志
INSERT INTO user_audit_log (user_id, action, old_values, changed_by)
VALUES (
OLD.id,
'DELETE',
JSON_OBJECT(
'username', OLD.username,
'email', OLD.email,
'status', OLD.status
),
USER()
);

-- 清理相关数据
DELETE FROM user_profiles WHERE user_id = OLD.id;
DELETE FROM user_sessions WHERE user_id = OLD.id;
END //
DELIMITER ;

2. 复杂业务触发器

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
-- 订单相关表
CREATE TABLE orders (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
total_amount DECIMAL(10,2) NOT NULL,
status TINYINT DEFAULT 1, -- 1:待支付 2:已支付 3:已发货 4:已完成 5:已取消
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

CREATE TABLE products (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(200) NOT NULL,
price DECIMAL(10,2) NOT NULL,
stock_quantity INT NOT NULL DEFAULT 0,
sales_count INT NOT NULL DEFAULT 0,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

CREATE TABLE order_items (
id INT AUTO_INCREMENT PRIMARY KEY,
order_id INT NOT NULL,
product_id INT NOT NULL,
quantity INT NOT NULL,
unit_price DECIMAL(10,2) NOT NULL,
total_price DECIMAL(10,2) NOT NULL
);

-- 库存管理触发器
DELIMITER //
CREATE TRIGGER tr_order_items_after_insert
AFTER INSERT ON order_items
FOR EACH ROW
BEGIN
-- 减少库存
UPDATE products
SET stock_quantity = stock_quantity - NEW.quantity,
updated_at = NOW()
WHERE id = NEW.product_id;

-- 检查库存是否充足
IF (SELECT stock_quantity FROM products WHERE id = NEW.product_id) < 0 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '库存不足,无法完成订单';
END IF;
END //
DELIMITER ;

-- 订单状态变更触发器
DELIMITER //
CREATE TRIGGER tr_orders_after_update
AFTER UPDATE ON orders
FOR EACH ROW
BEGIN
-- 订单完成时更新销量
IF OLD.status != 4 AND NEW.status = 4 THEN
UPDATE products p
INNER JOIN order_items oi ON p.id = oi.product_id
SET p.sales_count = p.sales_count + oi.quantity
WHERE oi.order_id = NEW.id;
END IF;

-- 订单取消时恢复库存
IF OLD.status IN (1, 2, 3) AND NEW.status = 5 THEN
UPDATE products p
INNER JOIN order_items oi ON p.id = oi.product_id
SET p.stock_quantity = p.stock_quantity + oi.quantity
WHERE oi.order_id = NEW.id;
END IF;

-- 记录状态变更历史
INSERT INTO order_status_history (order_id, old_status, new_status, changed_at)
VALUES (NEW.id, OLD.status, NEW.status, NOW());
END //
DELIMITER ;

-- 用户积分触发器
CREATE TABLE user_points (
user_id INT PRIMARY KEY,
total_points INT DEFAULT 0,
available_points INT DEFAULT 0,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

DELIMITER //
CREATE TRIGGER tr_orders_points_update
AFTER UPDATE ON orders
FOR EACH ROW
BEGIN
DECLARE points_earned INT DEFAULT 0;

-- 订单完成时给用户加积分
IF OLD.status != 4 AND NEW.status = 4 THEN
-- 每消费1元得1积分
SET points_earned = FLOOR(NEW.total_amount);

-- 更新用户积分
INSERT INTO user_points (user_id, total_points, available_points)
VALUES (NEW.user_id, points_earned, points_earned)
ON DUPLICATE KEY UPDATE
total_points = total_points + points_earned,
available_points = available_points + points_earned,
updated_at = NOW();

-- 记录积分变动
INSERT INTO point_transactions (user_id, order_id, points, type, description, created_at)
VALUES (NEW.user_id, NEW.id, points_earned, 'earn', '订单完成奖励', NOW());
END IF;
END //
DELIMITER ;

3. 数据同步触发器

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
-- 数据同步场景:主表和统计表
CREATE TABLE user_statistics (
user_id INT PRIMARY KEY,
total_orders INT DEFAULT 0,
total_amount DECIMAL(12,2) DEFAULT 0,
avg_order_amount DECIMAL(10,2) DEFAULT 0,
last_order_date DATE,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

-- 订单统计同步触发器
DELIMITER //
CREATE TRIGGER tr_sync_user_statistics
AFTER INSERT ON orders
FOR EACH ROW
BEGIN
INSERT INTO user_statistics (
user_id, total_orders, total_amount, avg_order_amount, last_order_date
)
VALUES (
NEW.user_id, 1, NEW.total_amount, NEW.total_amount, DATE(NEW.created_at)
)
ON DUPLICATE KEY UPDATE
total_orders = total_orders + 1,
total_amount = total_amount + NEW.total_amount,
avg_order_amount = (total_amount + NEW.total_amount) / (total_orders + 1),
last_order_date = DATE(NEW.created_at),
updated_at = NOW();
END //
DELIMITER ;

-- 订单更新统计同步
DELIMITER //
CREATE TRIGGER tr_sync_user_statistics_update
AFTER UPDATE ON orders
FOR EACH ROW
BEGIN
DECLARE v_total_orders INT DEFAULT 0;
DECLARE v_total_amount DECIMAL(12,2) DEFAULT 0;
DECLARE v_last_order_date DATE;

-- 重新计算用户统计数据
SELECT
COUNT(*),
COALESCE(SUM(total_amount), 0),
MAX(DATE(created_at))
INTO v_total_orders, v_total_amount, v_last_order_date
FROM orders
WHERE user_id = NEW.user_id AND status IN (2, 3, 4);

-- 更新统计表
UPDATE user_statistics
SET
total_orders = v_total_orders,
total_amount = v_total_amount,
avg_order_amount = CASE WHEN v_total_orders > 0 THEN v_total_amount / v_total_orders ELSE 0 END,
last_order_date = v_last_order_date,
updated_at = NOW()
WHERE user_id = NEW.user_id;
END //
DELIMITER ;

事件调度器应用

1. 启用和基本配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
-- 查看事件调度器状态
SHOW VARIABLES LIKE 'event_scheduler';

-- 启用事件调度器
SET GLOBAL event_scheduler = ON;

-- 查看当前事件
SHOW EVENTS;

-- 查看事件详细信息
SELECT
EVENT_SCHEMA,
EVENT_NAME,
EVENT_TYPE,
EXECUTE_AT,
INTERVAL_VALUE,
INTERVAL_FIELD,
STATUS,
CREATED,
LAST_EXECUTED,
EVENT_COMMENT
FROM information_schema.EVENTS
WHERE EVENT_SCHEMA = DATABASE();

2. 定时清理任务

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
-- 创建日志清理事件
DELIMITER //
CREATE EVENT ev_cleanup_logs
ON SCHEDULE EVERY 1 DAY
STARTS '2023-08-28 02:00:00'
COMMENT '每天凌晨2点清理30天前的日志'
DO
BEGIN
DECLARE deleted_count INT DEFAULT 0;

-- 清理用户审计日志
DELETE FROM user_audit_log
WHERE changed_at < DATE_SUB(NOW(), INTERVAL 30 DAY);
SET deleted_count = ROW_COUNT();

-- 清理会话日志
DELETE FROM user_sessions
WHERE created_at < DATE_SUB(NOW(), INTERVAL 7 DAY)
AND status = 'expired';
SET deleted_count = deleted_count + ROW_COUNT();

-- 清理临时文件记录
DELETE FROM temp_files
WHERE created_at < DATE_SUB(NOW(), INTERVAL 1 DAY);
SET deleted_count = deleted_count + ROW_COUNT();

-- 记录清理结果
INSERT INTO system_logs (type, message, created_at)
VALUES ('cleanup', CONCAT('清理完成,删除 ', deleted_count, ' 条记录'), NOW());
END //
DELIMITER ;

-- 创建数据备份事件
DELIMITER //
CREATE EVENT ev_daily_backup
ON SCHEDULE EVERY 1 DAY
STARTS '2023-08-28 01:00:00'
COMMENT '每天凌晨1点执行数据备份'
DO
BEGIN
DECLARE backup_table_name VARCHAR(100);
DECLARE backup_date VARCHAR(10);

SET backup_date = DATE_FORMAT(NOW(), '%Y%m%d');

-- 备份用户数据
SET backup_table_name = CONCAT('users_backup_', backup_date);
SET @sql = CONCAT('CREATE TABLE ', backup_table_name, ' AS SELECT * FROM users');
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

-- 备份订单数据
SET backup_table_name = CONCAT('orders_backup_', backup_date);
SET @sql = CONCAT('CREATE TABLE ', backup_table_name, ' AS SELECT * FROM orders WHERE DATE(created_at) = CURDATE() - INTERVAL 1 DAY');
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

-- 记录备份日志
INSERT INTO system_logs (type, message, created_at)
VALUES ('backup', CONCAT('数据备份完成:', backup_date), NOW());
END //
DELIMITER ;

3. 业务统计事件

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
-- 创建每小时统计事件
DELIMITER //
CREATE EVENT ev_hourly_statistics
ON SCHEDULE EVERY 1 HOUR
STARTS '2023-08-28 00:00:00'
COMMENT '每小时统计业务数据'
DO
BEGIN
DECLARE current_hour VARCHAR(13);
DECLARE new_users INT DEFAULT 0;
DECLARE new_orders INT DEFAULT 0;
DECLARE total_sales DECIMAL(12,2) DEFAULT 0;

SET current_hour = DATE_FORMAT(NOW() - INTERVAL 1 HOUR, '%Y-%m-%d %H');

-- 统计新用户数
SELECT COUNT(*) INTO new_users
FROM users
WHERE DATE_FORMAT(created_at, '%Y-%m-%d %H') = current_hour;

-- 统计新订单数和销售额
SELECT COUNT(*), COALESCE(SUM(total_amount), 0)
INTO new_orders, total_sales
FROM orders
WHERE DATE_FORMAT(created_at, '%Y-%m-%d %H') = current_hour
AND status IN (2, 3, 4);

-- 插入统计数据
INSERT INTO hourly_statistics (
stat_hour, new_users, new_orders, total_sales, created_at
) VALUES (
CONCAT(current_hour, ':00:00'), new_users, new_orders, total_sales, NOW()
);

-- 清理7天前的小时统计
DELETE FROM hourly_statistics
WHERE created_at < DATE_SUB(NOW(), INTERVAL 7 DAY);
END //
DELIMITER ;

-- 创建每日报表事件
DELIMITER //
CREATE EVENT ev_daily_report
ON SCHEDULE EVERY 1 DAY
STARTS '2023-08-28 23:30:00'
COMMENT '每天23:30生成日报'
DO
BEGIN
DECLARE report_date DATE;
DECLARE daily_users INT DEFAULT 0;
DECLARE daily_orders INT DEFAULT 0;
DECLARE daily_sales DECIMAL(12,2) DEFAULT 0;
DECLARE daily_refunds DECIMAL(12,2) DEFAULT 0;

SET report_date = CURDATE();

-- 统计当日数据
SELECT COUNT(*) INTO daily_users
FROM users
WHERE DATE(created_at) = report_date;

SELECT COUNT(*), COALESCE(SUM(total_amount), 0)
INTO daily_orders, daily_sales
FROM orders
WHERE DATE(created_at) = report_date
AND status IN (2, 3, 4);

SELECT COALESCE(SUM(refund_amount), 0) INTO daily_refunds
FROM orders
WHERE DATE(updated_at) = report_date
AND status = 5;

-- 插入日报数据
INSERT INTO daily_reports (
report_date, new_users, total_orders, total_sales,
total_refunds, net_sales, created_at
) VALUES (
report_date, daily_users, daily_orders, daily_sales,
daily_refunds, daily_sales - daily_refunds, NOW()
);

-- 发送报表通知(这里可以调用存储过程发送邮件或消息)
CALL SendDailyReportNotification(report_date, daily_users, daily_orders, daily_sales);
END //
DELIMITER ;

4. 系统维护事件

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
-- 创建索引优化事件
DELIMITER //
CREATE EVENT ev_optimize_tables
ON SCHEDULE EVERY 1 WEEK
STARTS '2023-08-28 03:00:00'
COMMENT '每周优化表和索引'
DO
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE table_name VARCHAR(64);
DECLARE optimize_cursor CURSOR FOR
SELECT TABLE_NAME
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_TYPE = 'BASE TABLE'
AND TABLE_NAME NOT LIKE '%_backup_%';

DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;

OPEN optimize_cursor;

optimize_loop: LOOP
FETCH optimize_cursor INTO table_name;

IF done THEN
LEAVE optimize_loop;
END IF;

-- 优化表
SET @sql = CONCAT('OPTIMIZE TABLE ', table_name);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

-- 分析表
SET @sql = CONCAT('ANALYZE TABLE ', table_name);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

END LOOP;

CLOSE optimize_cursor;

-- 记录优化日志
INSERT INTO system_logs (type, message, created_at)
VALUES ('optimize', '表优化完成', NOW());
END //
DELIMITER ;

-- 创建会话清理事件
DELIMITER //
CREATE EVENT ev_cleanup_sessions
ON SCHEDULE EVERY 30 MINUTE
COMMENT '每30分钟清理过期会话'
DO
BEGIN
DECLARE expired_count INT DEFAULT 0;

-- 清理过期会话
DELETE FROM user_sessions
WHERE expires_at < NOW()
OR (last_activity < DATE_SUB(NOW(), INTERVAL 2 HOUR) AND status = 'active');

SET expired_count = ROW_COUNT();

-- 更新在线用户统计
UPDATE system_statistics
SET online_users = (
SELECT COUNT(DISTINCT user_id)
FROM user_sessions
WHERE status = 'active'
AND last_activity > DATE_SUB(NOW(), INTERVAL 30 MINUTE)
),
updated_at = NOW()
WHERE stat_type = 'online_users';

-- 记录清理日志
IF expired_count > 0 THEN
INSERT INTO system_logs (type, message, created_at)
VALUES ('session_cleanup', CONCAT('清理过期会话 ', expired_count, ' 个'), NOW());
END IF;
END //
DELIMITER ;

5. 一次性事件

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
-- 创建一次性数据迁移事件
DELIMITER //
CREATE EVENT ev_migrate_old_data
ON SCHEDULE AT '2023-09-01 02:00:00'
COMMENT '一次性数据迁移任务'
DO
BEGIN
DECLARE migration_count INT DEFAULT 0;

-- 迁移旧用户数据
INSERT INTO new_users (old_user_id, username, email, created_at)
SELECT id, username, email, created_at
FROM old_users
WHERE migrated = 0;

SET migration_count = ROW_COUNT();

-- 标记已迁移
UPDATE old_users
SET migrated = 1, migrated_at = NOW()
WHERE migrated = 0;

-- 记录迁移结果
INSERT INTO system_logs (type, message, created_at)
VALUES ('migration', CONCAT('数据迁移完成,迁移用户 ', migration_count, ' 个'), NOW());

-- 迁移完成后删除事件
DROP EVENT IF EXISTS ev_migrate_old_data;
END //
DELIMITER ;

-- 创建临时促销活动事件
DELIMITER //
CREATE EVENT ev_flash_sale_start
ON SCHEDULE AT '2023-09-15 10:00:00'
COMMENT '闪购活动开始'
DO
BEGIN
-- 更新商品价格
UPDATE products
SET
flash_sale_price = price * 0.5,
flash_sale_start = NOW(),
flash_sale_end = DATE_ADD(NOW(), INTERVAL 2 HOUR),
is_flash_sale = 1
WHERE category_id IN (1, 2, 3)
AND stock_quantity > 10;

-- 记录活动开始
INSERT INTO system_logs (type, message, created_at)
VALUES ('promotion', '闪购活动开始', NOW());

-- 创建活动结束事件
CREATE EVENT ev_flash_sale_end
ON SCHEDULE AT '2023-09-15 12:00:00'
DO
BEGIN
UPDATE products
SET
flash_sale_price = NULL,
flash_sale_start = NULL,
flash_sale_end = NULL,
is_flash_sale = 0
WHERE is_flash_sale = 1;

INSERT INTO system_logs (type, message, created_at)
VALUES ('promotion', '闪购活动结束', NOW());

DROP EVENT IF EXISTS ev_flash_sale_end;
END;
END //
DELIMITER ;

监控和管理

1. 触发器监控

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
-- 查看所有触发器
SELECT
TRIGGER_SCHEMA as '数据库',
TRIGGER_NAME as '触发器名',
EVENT_MANIPULATION as '触发事件',
EVENT_OBJECT_TABLE as '目标表',
ACTION_TIMING as '触发时机',
CREATED as '创建时间'
FROM information_schema.TRIGGERS
WHERE TRIGGER_SCHEMA = DATABASE()
ORDER BY EVENT_OBJECT_TABLE, ACTION_TIMING, EVENT_MANIPULATION;

-- 创建触发器性能监控
CREATE TABLE trigger_performance_log (
id INT AUTO_INCREMENT PRIMARY KEY,
trigger_name VARCHAR(64),
table_name VARCHAR(64),
execution_time DECIMAL(10,6),
rows_affected INT,
executed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 在触发器中添加性能监控
DELIMITER //
CREATE TRIGGER tr_monitor_performance
AFTER INSERT ON test_table
FOR EACH ROW
BEGIN
DECLARE start_time DECIMAL(20,6);
DECLARE end_time DECIMAL(20,6);

SET start_time = UNIX_TIMESTAMP(NOW(6));

-- 业务逻辑处理
-- ...

SET end_time = UNIX_TIMESTAMP(NOW(6));

-- 记录性能数据
INSERT INTO trigger_performance_log (
trigger_name, table_name, execution_time, rows_affected
) VALUES (
'tr_monitor_performance', 'test_table',
end_time - start_time, 1
);
END //
DELIMITER ;

2. 事件监控

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
-- 创建事件执行日志表
CREATE TABLE event_execution_log (
id INT AUTO_INCREMENT PRIMARY KEY,
event_name VARCHAR(64),
execution_start TIMESTAMP,
execution_end TIMESTAMP,
execution_time DECIMAL(10,3),
status ENUM('SUCCESS', 'ERROR'),
error_message TEXT,
rows_affected INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 在事件中添加监控代码
DELIMITER //
CREATE EVENT ev_monitored_cleanup
ON SCHEDULE EVERY 1 HOUR
COMMENT '带监控的清理任务'
DO
BEGIN
DECLARE start_time TIMESTAMP DEFAULT NOW();
DECLARE affected_rows INT DEFAULT 0;
DECLARE error_msg TEXT DEFAULT '';
DECLARE exit_code INT DEFAULT 0;

-- 异常处理
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
GET DIAGNOSTICS CONDITION 1
error_msg = MESSAGE_TEXT;
SET exit_code = 1;
END;

-- 执行清理任务
DELETE FROM temp_data WHERE created_at < DATE_SUB(NOW(), INTERVAL 1 HOUR);
SET affected_rows = ROW_COUNT();

-- 记录执行日志
INSERT INTO event_execution_log (
event_name, execution_start, execution_end,
execution_time, status, error_message, rows_affected
) VALUES (
'ev_monitored_cleanup', start_time, NOW(),
TIMESTAMPDIFF(MICROSECOND, start_time, NOW()) / 1000,
CASE WHEN exit_code = 0 THEN 'SUCCESS' ELSE 'ERROR' END,
error_msg, affected_rows
);
END //
DELIMITER ;

-- 查看事件执行统计
SELECT
event_name,
COUNT(*) as execution_count,
AVG(execution_time) as avg_execution_time,
MAX(execution_time) as max_execution_time,
SUM(CASE WHEN status = 'ERROR' THEN 1 ELSE 0 END) as error_count,
MAX(created_at) as last_execution
FROM event_execution_log
WHERE created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)
GROUP BY event_name
ORDER BY avg_execution_time DESC;

最佳实践和注意事项

1. 触发器最佳实践

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
-- 好的触发器设计示例
DELIMITER //
CREATE TRIGGER tr_good_practice_example
BEFORE UPDATE ON users
FOR EACH ROW
BEGIN
-- 1. 简单快速的验证
IF NEW.email = '' THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '邮箱不能为空';
END IF;

-- 2. 避免复杂的业务逻辑
-- 不要在触发器中执行耗时操作

-- 3. 使用适当的错误处理
IF NEW.status NOT IN (0, 1, 2) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '无效的用户状态';
END IF;

-- 4. 记录必要的审计信息
SET NEW.updated_at = NOW();
SET NEW.updated_by = USER();
END //
DELIMITER ;

-- 避免的触发器反模式
/*
DELIMITER //
CREATE TRIGGER tr_bad_practice_example
AFTER INSERT ON orders
FOR EACH ROW
BEGIN
-- 错误1:在触发器中执行复杂查询
SELECT COUNT(*) FROM order_items WHERE order_id = NEW.id;

-- 错误2:调用外部API或发送邮件
-- CALL send_email_notification(NEW.user_id);

-- 错误3:修改其他不相关的表
UPDATE global_statistics SET total_orders = total_orders + 1;

-- 错误4:没有错误处理
INSERT INTO some_table VALUES (NEW.id, NULL); -- 可能失败
END //
DELIMITER ;
*/

2. 事件调度器最佳实践

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
-- 事件设计最佳实践
DELIMITER //
CREATE EVENT ev_best_practice_example
ON SCHEDULE EVERY 1 DAY
STARTS '2023-08-28 02:00:00'
ON COMPLETION PRESERVE
ENABLE
COMMENT '最佳实践示例事件'
DO
BEGIN
DECLARE start_time TIMESTAMP DEFAULT NOW();
DECLARE affected_rows INT DEFAULT 0;
DECLARE error_occurred BOOLEAN DEFAULT FALSE;
DECLARE error_message TEXT DEFAULT '';

-- 1. 添加错误处理
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
SET error_occurred = TRUE;
GET DIAGNOSTICS CONDITION 1 error_message = MESSAGE_TEXT;
ROLLBACK;
END;

-- 2. 使用事务确保数据一致性
START TRANSACTION;

-- 3. 执行具体任务
DELETE FROM expired_sessions WHERE expires_at < NOW();
SET affected_rows = ROW_COUNT();

-- 4. 记录执行结果
INSERT INTO event_logs (
event_name, start_time, end_time, affected_rows,
status, error_message
) VALUES (
'ev_best_practice_example', start_time, NOW(), affected_rows,
CASE WHEN error_occurred THEN 'ERROR' ELSE 'SUCCESS' END,
error_message
);

-- 5. 提交事务
IF NOT error_occurred THEN
COMMIT;
END IF;
END //
DELIMITER ;

3. 性能优化建议

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
-- 触发器性能优化
DELIMITER //
CREATE TRIGGER tr_optimized_audit
AFTER UPDATE ON users
FOR EACH ROW
BEGIN
-- 优化1:只在必要时记录审计日志
IF OLD.status != NEW.status OR OLD.email != NEW.email THEN
INSERT INTO user_audit_log (
user_id, action, old_values, new_values, changed_at
) VALUES (
NEW.id, 'UPDATE',
JSON_OBJECT('status', OLD.status, 'email', OLD.email),
JSON_OBJECT('status', NEW.status, 'email', NEW.email),
NOW()
);
END IF;

-- 优化2:批量操作而不是逐行处理
-- 避免在触发器中使用游标或循环
END //
DELIMITER ;

-- 事件性能优化
DELIMITER //
CREATE EVENT ev_optimized_cleanup
ON SCHEDULE EVERY 1 DAY
STARTS '2023-08-28 03:00:00'
COMMENT '优化的清理事件'
DO
BEGIN
DECLARE batch_size INT DEFAULT 1000;
DECLARE total_deleted INT DEFAULT 0;
DECLARE current_deleted INT DEFAULT 0;

-- 分批删除,避免长时间锁表
REPEAT
DELETE FROM large_log_table
WHERE created_at < DATE_SUB(NOW(), INTERVAL 30 DAY)
LIMIT batch_size;

SET current_deleted = ROW_COUNT();
SET total_deleted = total_deleted + current_deleted;

-- 给其他操作让出资源
SELECT SLEEP(0.1);

UNTIL current_deleted = 0 END REPEAT;

-- 记录清理结果
INSERT INTO cleanup_logs (table_name, deleted_rows, cleanup_date)
VALUES ('large_log_table', total_deleted, NOW());
END //
DELIMITER ;

4. 安全和权限管理

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
-- 创建专门的事件执行用户
CREATE USER 'event_scheduler'@'localhost' IDENTIFIED BY 'secure_password';

-- 授予必要的权限
GRANT SELECT, INSERT, UPDATE, DELETE ON database_name.* TO 'event_scheduler'@'localhost';
GRANT EVENT ON database_name.* TO 'event_scheduler'@'localhost';

-- 创建安全的触发器
DELIMITER //
CREATE TRIGGER tr_secure_example
BEFORE INSERT ON sensitive_table
FOR EACH ROW
BEGIN
-- 验证数据来源
IF USER() NOT LIKE '%@localhost' THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '不允许远程插入敏感数据';
END IF;

-- 数据脱敏
IF NEW.phone IS NOT NULL THEN
SET NEW.phone = CONCAT(LEFT(NEW.phone, 3), '****', RIGHT(NEW.phone, 4));
END IF;

-- 记录操作日志
INSERT INTO security_audit_log (
table_name, operation, user_name, ip_address, created_at
) VALUES (
'sensitive_table', 'INSERT', USER(),
COALESCE(@client_ip, 'unknown'), NOW()
);
END //
DELIMITER ;

5. 故障排除和调试

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
-- 创建调试辅助表
CREATE TABLE debug_log (
id INT AUTO_INCREMENT PRIMARY KEY,
source_type ENUM('TRIGGER', 'EVENT'),
source_name VARCHAR(100),
debug_level ENUM('INFO', 'WARN', 'ERROR'),
message TEXT,
data JSON,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 调试触发器
DELIMITER //
CREATE TRIGGER tr_debug_example
AFTER INSERT ON test_table
FOR EACH ROW
BEGIN
-- 记录调试信息
INSERT INTO debug_log (source_type, source_name, debug_level, message, data)
VALUES (
'TRIGGER', 'tr_debug_example', 'INFO',
'触发器执行开始',
JSON_OBJECT('new_id', NEW.id, 'user', USER())
);

-- 业务逻辑
IF NEW.amount > 10000 THEN
INSERT INTO debug_log (source_type, source_name, debug_level, message, data)
VALUES (
'TRIGGER', 'tr_debug_example', 'WARN',
'检测到大额交易',
JSON_OBJECT('amount', NEW.amount, 'user_id', NEW.user_id)
);
END IF;

INSERT INTO debug_log (source_type, source_name, debug_level, message)
VALUES ('TRIGGER', 'tr_debug_example', 'INFO', '触发器执行完成');
END //
DELIMITER ;

-- 查看调试日志
SELECT
source_name,
debug_level,
message,
data,
created_at
FROM debug_log
WHERE source_type = 'TRIGGER'
AND created_at >= DATE_SUB(NOW(), INTERVAL 1 HOUR)
ORDER BY created_at DESC;

6. 维护和管理工具

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
-- 触发器管理存储过程
DELIMITER //
CREATE PROCEDURE ManageTriggers(
IN action ENUM('ENABLE', 'DISABLE', 'LIST', 'DROP'),
IN trigger_pattern VARCHAR(100)
)
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE trigger_name VARCHAR(64);
DECLARE table_name VARCHAR(64);
DECLARE trigger_cursor CURSOR FOR
SELECT TRIGGER_NAME, EVENT_OBJECT_TABLE
FROM information_schema.TRIGGERS
WHERE TRIGGER_SCHEMA = DATABASE()
AND TRIGGER_NAME LIKE CONCAT('%', trigger_pattern, '%');

DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;

CASE action
WHEN 'LIST' THEN
SELECT
TRIGGER_NAME as '触发器名',
EVENT_OBJECT_TABLE as '表名',
EVENT_MANIPULATION as '事件',
ACTION_TIMING as '时机',
CREATED as '创建时间'
FROM information_schema.TRIGGERS
WHERE TRIGGER_SCHEMA = DATABASE()
AND TRIGGER_NAME LIKE CONCAT('%', trigger_pattern, '%');

WHEN 'DROP' THEN
OPEN trigger_cursor;
drop_loop: LOOP
FETCH trigger_cursor INTO trigger_name, table_name;
IF done THEN LEAVE drop_loop; END IF;

SET @sql = CONCAT('DROP TRIGGER IF EXISTS ', trigger_name);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

SELECT CONCAT('已删除触发器: ', trigger_name) as result;
END LOOP;
CLOSE trigger_cursor;
END CASE;
END //
DELIMITER ;

-- 事件管理存储过程
DELIMITER //
CREATE PROCEDURE ManageEvents(
IN action ENUM('ENABLE', 'DISABLE', 'LIST', 'DROP'),
IN event_pattern VARCHAR(100)
)
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE event_name VARCHAR(64);
DECLARE event_cursor CURSOR FOR
SELECT EVENT_NAME
FROM information_schema.EVENTS
WHERE EVENT_SCHEMA = DATABASE()
AND EVENT_NAME LIKE CONCAT('%', event_pattern, '%');

DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;

CASE action
WHEN 'LIST' THEN
SELECT
EVENT_NAME as '事件名',
EVENT_TYPE as '类型',
EXECUTE_AT as '执行时间',
INTERVAL_VALUE as '间隔值',
INTERVAL_FIELD as '间隔单位',
STATUS as '状态',
LAST_EXECUTED as '最后执行'
FROM information_schema.EVENTS
WHERE EVENT_SCHEMA = DATABASE()
AND EVENT_NAME LIKE CONCAT('%', event_pattern, '%');

WHEN 'ENABLE' THEN
OPEN event_cursor;
enable_loop: LOOP
FETCH event_cursor INTO event_name;
IF done THEN LEAVE enable_loop; END IF;

SET @sql = CONCAT('ALTER EVENT ', event_name, ' ENABLE');
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

SELECT CONCAT('已启用事件: ', event_name) as result;
END LOOP;
CLOSE event_cursor;

WHEN 'DISABLE' THEN
OPEN event_cursor;
disable_loop: LOOP
FETCH event_cursor INTO event_name;
IF done THEN LEAVE disable_loop; END IF;

SET @sql = CONCAT('ALTER EVENT ', event_name, ' DISABLE');
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

SELECT CONCAT('已禁用事件: ', event_name) as result;
END LOOP;
CLOSE event_cursor;
END CASE;
END //
DELIMITER ;

-- 使用管理工具
CALL ManageTriggers('LIST', 'user');
CALL ManageEvents('LIST', 'cleanup');

总结

MySQL触发器和事件调度器是实现数据库自动化的强大工具:

触发器适用场景:

  • 数据验证和约束
  • 审计日志记录
  • 数据同步和冗余维护
  • 业务规则自动执行

事件调度器适用场景:

  • 定期数据清理
  • 自动备份任务
  • 统计报表生成
  • 系统维护操作

使用建议:

  1. 保持简单:触发器和事件应该执行简单、快速的操作
  2. 错误处理:添加适当的异常处理机制
  3. 性能监控:监控执行性能,避免影响主业务
  4. 文档记录:详细记录每个触发器和事件的用途
  5. 测试充分:在生产环境部署前进行充分测试

通过合理使用触发器和事件调度器,可以大大提高数据库的自动化程度,减少手工维护工作,提高系统的可靠性和一致性。

本站由 提供部署服务