MySQL存储过程 #
一、存储过程概述 #
1.1 什么是存储过程 #
存储过程是一组预编译的SQL语句,存储在数据库中,可以通过名称调用执行。
text
┌─────────────────────────────────────┐
│ 存储过程 │
│ ┌─────────────────────────────┐ │
│ │ SQL语句集合 │ │
│ │ - 变量定义 │ │
│ │ - 流程控制 │ │
│ │ - 错误处理 │ │
│ └─────────────────────────────┘ │
│ ↓ │
│ 数据库执行 │
└─────────────────────────────────────┘
1.2 存储过程的优点 #
| 优点 | 说明 |
|---|---|
| 性能提升 | 预编译,减少网络传输 |
| 代码复用 | 一次编写,多次调用 |
| 安全性 | 可以控制数据访问权限 |
| 减少网络流量 | 只传输调用参数和结果 |
1.3 存储过程的缺点 #
| 缺点 | 说明 |
|---|---|
| 调试困难 | 调试工具有限 |
| 可移植性差 | 不同数据库语法不同 |
| 维护成本 | 业务逻辑分散 |
二、创建存储过程 #
2.1 基本语法 #
sql
CREATE PROCEDURE procedure_name([parameters])
BEGIN
-- SQL语句
END;
2.2 第一个存储过程 #
sql
-- 修改分隔符
DELIMITER //
CREATE PROCEDURE get_all_users()
BEGIN
SELECT * FROM users;
END //
DELIMITER ;
-- 调用存储过程
CALL get_all_users();
2.3 查看存储过程 #
sql
-- 查看存储过程状态
SHOW PROCEDURE STATUS WHERE db = 'mydb';
-- 查看存储过程定义
SHOW CREATE PROCEDURE get_all_users;
-- 从information_schema查询
SELECT
routine_name,
routine_definition
FROM information_schema.routines
WHERE routine_type = 'PROCEDURE' AND routine_schema = 'mydb';
2.4 删除存储过程 #
sql
DROP PROCEDURE get_all_users;
-- 如果存在则删除
DROP PROCEDURE IF EXISTS get_all_users;
三、参数 #
3.1 参数类型 #
| 类型 | 说明 |
|---|---|
| IN | 输入参数(默认) |
| OUT | 输出参数 |
| INOUT | 输入输出参数 |
3.2 IN参数 #
sql
DELIMITER //
CREATE PROCEDURE get_user_by_id(IN user_id INT)
BEGIN
SELECT * FROM users WHERE id = user_id;
END //
DELIMITER ;
-- 调用
CALL get_user_by_id(1);
3.3 OUT参数 #
sql
DELIMITER //
CREATE PROCEDURE get_user_count(OUT total INT)
BEGIN
SELECT COUNT(*) INTO total FROM users;
END //
DELIMITER ;
-- 调用
CALL get_user_count(@count);
SELECT @count;
3.4 INOUT参数 #
sql
DELIMITER //
CREATE PROCEDURE double_value(INOUT num INT)
BEGIN
SET num = num * 2;
END //
DELIMITER ;
-- 调用
SET @value = 10;
CALL double_value(@value);
SELECT @value; -- 结果:20
3.5 多个参数 #
sql
DELIMITER //
CREATE PROCEDURE get_users_by_age_range(
IN min_age INT,
IN max_age INT,
OUT total_count INT
)
BEGIN
SELECT * FROM users WHERE age BETWEEN min_age AND max_age;
SELECT COUNT(*) INTO total_count FROM users WHERE age BETWEEN min_age AND max_age;
END //
DELIMITER ;
-- 调用
CALL get_users_by_age_range(20, 30, @count);
SELECT @count;
四、变量 #
4.1 局部变量 #
sql
DELIMITER //
CREATE PROCEDURE test_variables()
BEGIN
-- 声明局部变量
DECLARE v_name VARCHAR(50) DEFAULT 'Unknown';
DECLARE v_age INT;
DECLARE v_count INT DEFAULT 0;
-- 赋值
SET v_name = 'John';
SET v_age = 25;
-- 使用SELECT INTO赋值
SELECT COUNT(*) INTO v_count FROM users;
-- 输出
SELECT v_name, v_age, v_count;
END //
DELIMITER ;
4.2 用户变量 #
sql
DELIMITER //
CREATE PROCEDURE test_user_variables()
BEGIN
-- 用户变量以@开头
SET @user_name = 'John';
SET @user_age = 25;
SELECT @user_name, @user_age;
END //
DELIMITER ;
-- 用户变量在会话中有效
SELECT @user_name;
4.3 系统变量 #
sql
DELIMITER //
CREATE PROCEDURE test_system_variables()
BEGIN
-- 查看系统变量
SELECT @@version;
SELECT @@autocommit;
SELECT @@max_connections;
-- 设置会话变量
SET SESSION autocommit = 0;
END //
DELIMITER ;
五、流程控制 #
5.1 IF语句 #
sql
DELIMITER //
CREATE PROCEDURE check_age(IN age INT)
BEGIN
IF age < 18 THEN
SELECT '未成年' AS result;
ELSEIF age < 60 THEN
SELECT '成年' AS result;
ELSE
SELECT '老年' AS result;
END IF;
END //
DELIMITER ;
-- 调用
CALL check_age(25);
5.2 CASE语句 #
sql
DELIMITER //
CREATE PROCEDURE get_grade(IN score INT)
BEGIN
DECLARE grade CHAR(1);
CASE
WHEN score >= 90 THEN SET grade = 'A';
WHEN score >= 80 THEN SET grade = 'B';
WHEN score >= 70 THEN SET grade = 'C';
WHEN score >= 60 THEN SET grade = 'D';
ELSE SET grade = 'F';
END CASE;
SELECT grade;
END //
DELIMITER ;
-- 另一种形式
DELIMITER //
CREATE PROCEDURE get_status_text(IN status INT)
BEGIN
CASE status
WHEN 1 THEN SELECT 'Active' AS status_text;
WHEN 0 THEN SELECT 'Inactive' AS status_text;
ELSE SELECT 'Unknown' AS status_text;
END CASE;
END //
DELIMITER ;
5.3 WHILE循环 #
sql
DELIMITER //
CREATE PROCEDURE sum_numbers(IN n INT, OUT result INT)
BEGIN
DECLARE i INT DEFAULT 1;
SET result = 0;
WHILE i <= n DO
SET result = result + i;
SET i = i + 1;
END WHILE;
END //
DELIMITER ;
-- 调用
CALL sum_numbers(100, @total);
SELECT @total; -- 5050
5.4 REPEAT循环 #
sql
DELIMITER //
CREATE PROCEDURE sum_repeat(IN n INT, OUT result INT)
BEGIN
DECLARE i INT DEFAULT 1;
SET result = 0;
REPEAT
SET result = result + i;
SET i = i + 1;
UNTIL i > n END REPEAT;
END //
DELIMITER ;
5.5 LOOP循环 #
sql
DELIMITER //
CREATE PROCEDURE sum_loop(IN n INT, OUT result INT)
BEGIN
DECLARE i INT DEFAULT 1;
SET result = 0;
my_loop: LOOP
SET result = result + i;
SET i = i + 1;
IF i > n THEN
LEAVE my_loop;
END IF;
END LOOP;
END //
DELIMITER ;
5.6 ITERATE语句 #
sql
DELIMITER //
CREATE PROCEDURE sum_even(IN n INT, OUT result INT)
BEGIN
DECLARE i INT DEFAULT 0;
SET result = 0;
my_loop: LOOP
SET i = i + 1;
IF i > n THEN
LEAVE my_loop;
END IF;
IF i % 2 != 0 THEN
ITERATE my_loop; -- 跳过奇数
END IF;
SET result = result + i;
END LOOP;
END //
DELIMITER ;
六、游标 #
6.1 游标基本用法 #
sql
DELIMITER //
CREATE PROCEDURE process_users()
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE v_id INT;
DECLARE v_name VARCHAR(50);
DECLARE v_email VARCHAR(100);
-- 声明游标
DECLARE cur_users CURSOR FOR
SELECT id, name, email FROM users;
-- 声明结束处理程序
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
-- 打开游标
OPEN cur_users;
-- 循环读取
read_loop: LOOP
FETCH cur_users INTO v_id, v_name, v_email;
IF done THEN
LEAVE read_loop;
END IF;
-- 处理数据
SELECT v_id, v_name, v_email;
END LOOP;
-- 关闭游标
CLOSE cur_users;
END //
DELIMITER ;
6.2 游标更新数据 #
sql
DELIMITER //
CREATE PROCEDURE update_user_status()
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE v_id INT;
DECLARE v_last_login DATETIME;
DECLARE cur_users CURSOR FOR
SELECT id, last_login FROM users;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cur_users;
update_loop: LOOP
FETCH cur_users INTO v_id, v_last_login;
IF done THEN
LEAVE update_loop;
END IF;
-- 更新状态
IF v_last_login < DATE_SUB(NOW(), INTERVAL 30 DAY) THEN
UPDATE users SET status = 0 WHERE id = v_id;
END IF;
END LOOP;
CLOSE cur_users;
END //
DELIMITER ;
七、错误处理 #
7.1 DECLARE HANDLER #
sql
DELIMITER //
CREATE PROCEDURE safe_insert(
IN p_name VARCHAR(50),
IN p_email VARCHAR(100)
)
BEGIN
-- 声明错误处理程序
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
SELECT 'Error occurred, transaction rolled back' AS message;
END;
START TRANSACTION;
INSERT INTO users (name, email) VALUES (p_name, p_email);
COMMIT;
SELECT 'Insert successful' AS message;
END //
DELIMITER ;
7.2 获取错误信息 #
sql
DELIMITER //
CREATE PROCEDURE insert_with_error_info(
IN p_name VARCHAR(50),
IN p_email VARCHAR(100)
)
BEGIN
DECLARE v_error_code CHAR(5) DEFAULT '00000';
DECLARE v_error_msg TEXT;
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
GET DIAGNOSTICS CONDITION 1
v_error_code = RETURNED_SQLSTATE,
v_error_msg = MESSAGE_TEXT;
SELECT CONCAT('Error: ', v_error_code, ' - ', v_error_msg) AS error;
END;
INSERT INTO users (name, email) VALUES (p_name, p_email);
END //
DELIMITER ;
7.3 自定义错误 #
sql
DELIMITER //
CREATE PROCEDURE check_user_age(IN p_age INT)
BEGIN
IF p_age < 0 OR p_age > 150 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Invalid age value';
END IF;
SELECT 'Age is valid' AS result;
END //
DELIMITER ;
八、存储过程示例 #
8.1 分页查询 #
sql
DELIMITER //
CREATE PROCEDURE get_users_page(
IN p_page INT,
IN p_page_size INT
)
BEGIN
DECLARE v_offset INT;
SET v_offset = (p_page - 1) * p_page_size;
SELECT * FROM users
ORDER BY id
LIMIT v_offset, p_page_size;
END //
DELIMITER ;
-- 调用:第2页,每页10条
CALL get_users_page(2, 10);
8.2 批量插入 #
sql
DELIMITER //
CREATE PROCEDURE batch_insert_users(IN p_count INT)
BEGIN
DECLARE i INT DEFAULT 1;
WHILE i <= p_count DO
INSERT INTO users (name, email, created_at)
VALUES (
CONCAT('User', i),
CONCAT('user', i, '@example.com'),
NOW()
);
SET i = i + 1;
END WHILE;
SELECT CONCAT('Inserted ', p_count, ' users') AS result;
END //
DELIMITER ;
8.3 统计报表 #
sql
DELIMITER //
CREATE PROCEDURE get_sales_report(
IN p_start_date DATE,
IN p_end_date DATE
)
BEGIN
-- 总销售额
SELECT
COUNT(*) AS total_orders,
SUM(amount) AS total_amount,
AVG(amount) AS avg_amount
FROM orders
WHERE order_date BETWEEN p_start_date AND p_end_date;
-- 每日销售额
SELECT
DATE(order_date) AS sale_date,
COUNT(*) AS order_count,
SUM(amount) AS daily_amount
FROM orders
WHERE order_date BETWEEN p_start_date AND p_end_date
GROUP BY DATE(order_date)
ORDER BY sale_date;
END //
DELIMITER ;
九、总结 #
存储过程要点:
| 元素 | 说明 |
|---|---|
| 参数 | IN, OUT, INOUT |
| 变量 | DECLARE, SET, SELECT INTO |
| 流程控制 | IF, CASE, WHILE, REPEAT, LOOP |
| 游标 | CURSOR, FETCH |
| 错误处理 | DECLARE HANDLER, SIGNAL |
最佳实践:
- 使用有意义的命名
- 添加注释说明
- 处理可能的错误
- 避免过于复杂的逻辑
- 定期维护和优化
下一步,让我们学习事务!
最后更新:2026-03-26