高级数据库对象
一、存储过程进阶
1.1 变量定义与使用
sql
DELIMITER $$
CREATE PROCEDURE sp_advanced_demo()
BEGIN
-- 局部变量声明
DECLARE v_count INT DEFAULT 0;
DECLARE v_name VARCHAR(50);
DECLARE v_salary DECIMAL(10,2);
DECLARE v_done INT DEFAULT FALSE;
-- 游标结束标志
DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_done = TRUE;
-- 赋值方式1:SET
SET v_count = 100;
-- 赋值方式2:SELECT INTO
SELECT name, salary INTO v_name, v_salary
FROM employee WHERE id = 1;
-- 输出
SELECT v_name, v_salary, v_count;
END$$
DELIMITER ;
1.2 流程控制
sql
DELIMITER $$
CREATE PROCEDURE sp_flow_control(IN p_score INT)
BEGIN
DECLARE v_level VARCHAR(20);
-- IF ... ELSEIF ... ELSE
IF p_score >= 90 THEN
SET v_level = '优秀';
ELSEIF p_score >= 80 THEN
SET v_level = '良好';
ELSEIF p_score >= 60 THEN
SET v_level = '及格';
ELSE
SET v_level = '不及格';
END IF;
SELECT v_level AS score_level;
-- CASE 分支
CASE
WHEN p_score >= 90 THEN SELECT 'A' AS grade;
WHEN p_score >= 80 THEN SELECT 'B' AS grade;
WHEN p_score >= 70 THEN SELECT 'C' AS grade;
ELSE SELECT 'D' AS grade;
END CASE;
END$$
DELIMITER ;
1.3 循环控制
sql
DELIMITER $$
CREATE PROCEDURE sp_loop_demo(IN p_count INT)
BEGIN
DECLARE i INT DEFAULT 1;
DECLARE result INT DEFAULT 0;
-- WHILE 循环
WHILE i <= p_count DO
SET result = result + i;
SET i = i + 1;
END WHILE;
SELECT result AS sum_result;
-- REPEAT 循环(类似 do-while)
SET i = 1;
SET result = 0;
REPEAT
SET result = result + i;
SET i = i + 1;
UNTIL i > p_count END REPEAT;
SELECT result AS sum_result2;
END$$
DELIMITER ;
1.4 游标使用
sql
DELIMITER $$
-- 使用游标逐行处理数据
CREATE PROCEDURE sp_calc_bonus()
BEGIN
DECLARE v_done INT DEFAULT FALSE;
DECLARE v_emp_id INT;
DECLARE v_salary DECIMAL(10,2);
DECLARE v_bonus DECIMAL(10,2);
-- 声明游标
DECLARE cur CURSOR FOR
SELECT id, salary FROM employee WHERE dept_id = 1;
-- 声明继续处理器
DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_done = TRUE;
-- 创建临时表存储结果
CREATE TEMPORARY TABLE IF NOT EXISTS tmp_bonus (
emp_id INT,
salary DECIMAL(10,2),
bonus DECIMAL(10,2)
);
-- 打开游标
OPEN cur;
-- 循环遍历
read_loop: LOOP
FETCH cur INTO v_emp_id, v_salary;
IF v_done THEN
LEAVE read_loop;
END IF;
-- 计算奖金(工资的 20%)
SET v_bonus = ROUND(v_salary * 0.2, 2);
INSERT INTO tmp_bonus (emp_id, salary, bonus)
VALUES (v_emp_id, v_salary, v_bonus);
END LOOP;
-- 关闭游标
CLOSE cur;
-- 返回结果
SELECT * FROM tmp_bonus;
-- 清理临时表
DROP TEMPORARY TABLE tmp_bonus;
END$$
DELIMITER ;
1.5 异常处理
sql
DELIMITER $$
CREATE PROCEDURE sp_safe_transfer(
IN from_id INT,
IN to_id INT,
IN amount DECIMAL(12,2)
)
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
-- 发生异常时回滚
ROLLBACK;
SELECT '转账失败,已回滚' AS msg;
END;
-- 声明处理警告
DECLARE EXIT HANDLER FOR SQLWARNING
BEGIN
ROLLBACK;
SELECT '发生警告,已回滚' AS msg;
END;
START TRANSACTION;
UPDATE account SET balance = balance - amount WHERE id = from_id;
UPDATE account SET balance = balance + amount WHERE id = to_id;
COMMIT;
SELECT '转账成功' AS msg;
END$$
DELIMITER ;
二、触发器(TRIGGER)
2.1 触发器语法
sql
-- 创建触发器
CREATE TRIGGER trigger_name
{BEFORE | AFTER} {INSERT | UPDATE | DELETE}
ON table_name
FOR EACH ROW
BEGIN
-- 触发器逻辑
-- OLD.column:修改前的值
-- NEW.column:修改后的值
END;
-- 查看触发器
SHOW TRIGGERS;
-- 删除触发器
DROP TRIGGER IF EXISTS trigger_name;
2.2 INSERT 触发器
sql
-- 记录新增用户日志
DELIMITER $$
CREATE TRIGGER trg_user_after_insert
AFTER INSERT ON users
FOR EACH ROW
BEGIN
INSERT INTO user_log(user_id, action, old_value, new_value, create_time)
VALUES (NEW.id, 'INSERT', NULL,
CONCAT('{"name":"', NEW.name, '","email":"', NEW.email, '"}'),
NOW());
END$$
DELIMITER ;
2.3 UPDATE 触发器
sql
-- 记录工资变更
DELIMITER $$
CREATE TRIGGER trg_salary_before_update
BEFORE UPDATE ON employee
FOR EACH ROW
BEGIN
IF OLD.salary != NEW.salary THEN
INSERT INTO salary_log(emp_id, old_salary, new_salary, change_time)
VALUES (OLD.id, OLD.salary, NEW.salary, NOW());
END IF;
END$$
DELIMITER ;
2.4 DELETE 触发器
sql
-- 删除用户时备份到回收站
DELIMITER $$
CREATE TRIGGER trg_user_before_delete
BEFORE DELETE ON users
FOR EACH ROW
BEGIN
INSERT INTO users_deleted(id, name, email, deleted_at)
VALUES (OLD.id, OLD.name, OLD.email, NOW());
END$$
DELIMITER ;
2.5 触发器使用场景与风险
适合场景:
sql
-- 1. 自动记录审计日志
-- 2. 数据完整性校验
-- 3. 冗余字段自动更新(如 order_count)
-- 4. 级联操作
风险与限制:
sql
-- 风险1:隐式逻辑,排查问题时容易被忽略
-- 风险2:触发器内操作多会拖慢 DML 性能
-- 风险3:嵌套触发器可能导致不可预知的行为
-- 风险4:无法在触发器中使用动态 SQL
-- 生产建议:触发器尽量少用,审计日志等场景建议在应用层实现
三、事件调度器(EVENT)
3.1 开启事件调度器
sql
-- 查看事件调度器状态
SHOW VARIABLES LIKE 'event_scheduler'; -- 默认 OFF
-- 开启
SET GLOBAL event_scheduler = ON;
-- my.cnf 持久化
[mysqld]
event_scheduler = ON
3.2 创建事件
sql
-- 一次性事件:明天凌晨 3 点执行
CREATE EVENT ev_clean_temp_data
ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 1 DAY
DO
DELETE FROM temp_data WHERE create_time < DATE_SUB(NOW(), INTERVAL 7 DAY);
-- 重复事件:每天凌晨 3 点执行
CREATE EVENT ev_daily_stats
ON SCHEDULE EVERY 1 DAY
STARTS '2024-01-01 03:00:00'
DO
BEGIN
-- 插入每日统计
INSERT INTO daily_stats(stats_date, user_count, order_count, revenue)
SELECT
CURDATE() - INTERVAL 1 DAY,
COUNT(DISTINCT user_id),
COUNT(*),
SUM(amount)
FROM orders
WHERE DATE(create_time) = CURDATE() - INTERVAL 1 DAY;
END;
-- 带过期的事件:从 2024-01-01 到 2024-12-31,每天执行
CREATE EVENT ev_quarterly_cleanup
ON SCHEDULE EVERY 1 DAY
STARTS '2024-01-01 00:00:00'
ENDS '2024-12-31 23:59:59'
DO
DELETE FROM logs WHERE create_time < DATE_SUB(NOW(), INTERVAL 90 DAY);
3.3 管理事件
sql
-- 查看事件
SHOW EVENTS;
-- 或
SELECT * FROM information_schema.EVENTS;
-- 修改事件
ALTER EVENT ev_daily_stats
ON SCHEDULE EVERY 6 HOUR
DO
INSERT INTO daily_stats(...);
-- 启用/禁用事件
ALTER EVENT ev_daily_stats ENABLE;
ALTER EVENT ev_daily_stats DISABLE;
-- 删除事件
DROP EVENT IF EXISTS ev_daily_stats;
3.4 实战:数据归档事件
sql
-- 需求:每个月 1 日凌晨 2 点,将 6 个月前已完成的订单归档到 orders_archive 表
DELIMITER $$
CREATE EVENT ev_archive_orders
ON SCHEDULE EVERY 1 MONTH
STARTS '2024-01-01 02:00:00'
DO
BEGIN
DECLARE v_archived_count INT DEFAULT 0;
-- 插入归档表
INSERT INTO orders_archive
SELECT * FROM orders
WHERE status = '已完成'
AND create_time < DATE_SUB(NOW(), INTERVAL 6 MONTH)
AND is_archived = 0;
SET v_archived_count = ROW_COUNT();
-- 标记原表已归档
UPDATE orders
SET is_archived = 1
WHERE status = '已完成'
AND create_time < DATE_SUB(NOW(), INTERVAL 6 MONTH)
AND is_archived = 0;
-- 记录归档日志
INSERT INTO archive_log(archive_time, table_name, record_count)
VALUES (NOW(), 'orders', v_archived_count);
END$$
DELIMITER ;
四、实战综合案例
案例:员工薪资管理系统
sql
-- 完整的薪资管理系统:触发器 + 存储过程 + 事件
-- 1. 薪资变更触发器:自动记录日志
DELIMITER $$
CREATE TRIGGER trg_salary_change
AFTER UPDATE ON employee
FOR EACH ROW
BEGIN
IF OLD.salary != NEW.salary THEN
INSERT INTO salary_change_log(
emp_id, old_salary, new_salary,
change_percent, operator, change_time
) VALUES (
OLD.id,
OLD.salary,
NEW.salary,
ROUND((NEW.salary - OLD.salary) / OLD.salary * 100, 2),
USER(),
NOW()
);
-- 如果降薪超过 20%,记录高优先级告警
IF NEW.salary < OLD.salary * 0.8 THEN
INSERT INTO alert_log(
alert_level, alert_type, message
) VALUES (
'HIGH', 'SALARY_DROP',
CONCAT('员工', OLD.name, '降薪超过20%:', OLD.salary, '→', NEW.salary)
);
END IF;
END IF;
END$$
-- 2. 批量调薪存储过程
CREATE PROCEDURE sp_batch_salary_adjust(
IN p_dept_id INT,
IN p_percentage DECIMAL(5,2),
IN p_reason VARCHAR(200)
)
BEGIN
DECLARE v_done INT DEFAULT FALSE;
DECLARE v_emp_id INT;
DECLARE v_old_salary DECIMAL(10,2);
DECLARE cur CURSOR FOR
SELECT id, salary FROM employee
WHERE dept_id = p_dept_id AND status = 1;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_done = TRUE;
START TRANSACTION;
OPEN cur;
adjust_loop: LOOP
FETCH cur INTO v_emp_id, v_old_salary;
IF v_done THEN
LEAVE adjust_loop;
END IF;
UPDATE employee
SET salary = ROUND(v_old_salary * (1 + p_percentage / 100), 2)
WHERE id = v_emp_id;
END LOOP;
CLOSE cur;
-- 记录调薪批次
INSERT INTO salary_adjust_batch(
dept_id, percentage, reason, operator, create_time
) VALUES (
p_dept_id, p_percentage, p_reason, USER(), NOW()
);
COMMIT;
SELECT CONCAT('部门', p_dept_id, '调薪完成,幅度:', p_percentage, '%') AS msg;
END$$
-- 3. 月度薪资统计事件
CREATE EVENT ev_monthly_salary_stats
ON SCHEDULE EVERY 1 MONTH
STARTS '2024-02-01 02:00:00'
DO
BEGIN
INSERT INTO salary_monthly_stats(
stats_month, dept_id,
emp_count, total_salary, avg_salary, max_salary, min_salary
)
SELECT
DATE_FORMAT(DATE_SUB(NOW(), INTERVAL 1 MONTH), '%Y-%m'),
d.id,
COUNT(e.id),
COALESCE(SUM(e.salary), 0),
COALESCE(ROUND(AVG(e.salary), 2), 0),
COALESCE(MAX(e.salary), 0),
COALESCE(MIN(e.salary), 0)
FROM department d
LEFT JOIN employee e ON d.id = e.dept_id AND e.status = 1
GROUP BY d.id, d.dept_name;
END$$
DELIMITER ;
总结
| 对象 | 核心语法 | 使用建议 |
|---|---|---|
| 存储过程进阶 | 变量、游标、流程控制、异常处理 | 批量操作用、复杂业务逻辑建议应用层实现 |
| 触发器 | BEFORE/AFTER INSERT/UPDATE/DELETE | 少用、注意性能影响和隐式逻辑 |
| 事件调度器 | CREATE EVENT ON SCHEDULE | 代替 crontab 做数据库定时任务 |