MariaDB存储过程 #
一、存储过程概述 #
1.1 什么是存储过程 #
text
存储过程概念
├── 预编译的SQL语句集合
├── 存储在数据库中
├── 可以接受参数
├── 可以返回结果
└── 可以包含流程控制
存储过程优点
├── 提高性能(预编译、减少网络传输)
├── 代码重用
├── 安全性(权限控制)
├── 减少网络流量
└── 业务逻辑封装
1.2 存储过程vs函数 #
| 特性 | 存储过程 | 函数 |
|---|---|---|
| 返回值 | 可以返回多个值 | 必须返回一个值 |
| 参数 | IN, OUT, INOUT | 只有IN |
| SQL语句 | 可以包含各种SQL | 有限制 |
| 调用方式 | CALL | SELECT |
| 事务 | 支持 | 不支持 |
二、创建存储过程 #
2.1 基本语法 #
sql
DELIMITER //
CREATE PROCEDURE procedure_name([parameters])
BEGIN
-- SQL statements
END //
DELIMITER ;
2.2 简单存储过程 #
sql
DELIMITER //
CREATE PROCEDURE get_all_users()
BEGIN
SELECT * FROM users;
END //
DELIMITER ;
-- 调用存储过程
CALL get_all_users();
2.3 带参数的存储过程 #
sql
DELIMITER //
-- IN参数
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);
-- OUT参数
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;
-- INOUT参数
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.1 局部变量 #
sql
DELIMITER //
CREATE PROCEDURE variable_example()
BEGIN
-- 声明变量
DECLARE v_name VARCHAR(50);
DECLARE v_count INT DEFAULT 0;
DECLARE v_total DECIMAL(10,2);
-- 赋值
SET v_name = 'John';
SELECT COUNT(*) INTO v_count FROM users;
SELECT SUM(balance) INTO v_total FROM users;
-- 使用变量
SELECT v_name, v_count, v_total;
END //
DELIMITER ;
3.2 用户变量 #
sql
DELIMITER //
CREATE PROCEDURE user_variable_example()
BEGIN
-- 用户变量(以@开头)
SET @user_name = 'John';
SET @user_count = (SELECT COUNT(*) FROM users);
SELECT @user_name, @user_count;
END //
DELIMITER ;
3.3 系统变量 #
sql
DELIMITER //
CREATE PROCEDURE system_variable_example()
BEGIN
-- 查看系统变量
SELECT @@autocommit;
SELECT @@max_connections;
-- 设置会话变量
SET SESSION sql_mode = 'STRICT_ALL_TABLES';
END //
DELIMITER ;
四、流程控制 #
4.1 IF语句 #
sql
DELIMITER //
CREATE PROCEDURE check_user_status(IN user_id INT)
BEGIN
DECLARE v_status INT;
DECLARE v_message VARCHAR(50);
SELECT status INTO v_status FROM users WHERE id = user_id;
IF v_status = 1 THEN
SET v_message = 'User is active';
ELSEIF v_status = 0 THEN
SET v_message = 'User is inactive';
ELSE
SET v_message = 'Unknown status';
END IF;
SELECT v_message AS result;
END //
DELIMITER ;
CALL check_user_status(1);
4.2 CASE语句 #
sql
DELIMITER //
CREATE PROCEDURE get_grade(IN score INT)
BEGIN
DECLARE v_grade CHAR(1);
CASE
WHEN score >= 90 THEN SET v_grade = 'A';
WHEN score >= 80 THEN SET v_grade = 'B';
WHEN score >= 70 THEN SET v_grade = 'C';
WHEN score >= 60 THEN SET v_grade = 'D';
ELSE SET v_grade = 'F';
END CASE;
SELECT v_grade AS grade;
END //
DELIMITER ;
-- 另一种形式
DELIMITER //
CREATE PROCEDURE get_status_name(IN status INT)
BEGIN
DECLARE v_name VARCHAR(20);
CASE status
WHEN 1 THEN SET v_name = 'Active';
WHEN 0 THEN SET v_name = 'Inactive';
ELSE SET v_name = 'Unknown';
END CASE;
SELECT v_name AS status_name;
END //
DELIMITER ;
4.3 WHILE循环 #
sql
DELIMITER //
CREATE PROCEDURE while_example(IN max_count INT)
BEGIN
DECLARE i INT DEFAULT 0;
DECLARE result VARCHAR(1000) DEFAULT '';
WHILE i < max_count DO
SET result = CONCAT(result, i, ' ');
SET i = i + 1;
END WHILE;
SELECT result;
END //
DELIMITER ;
CALL while_example(5); -- 0 1 2 3 4
4.4 REPEAT循环 #
sql
DELIMITER //
CREATE PROCEDURE repeat_example(IN max_count INT)
BEGIN
DECLARE i INT DEFAULT 0;
DECLARE result VARCHAR(1000) DEFAULT '';
REPEAT
SET result = CONCAT(result, i, ' ');
SET i = i + 1;
UNTIL i >= max_count END REPEAT;
SELECT result;
END //
DELIMITER ;
CALL repeat_example(5); -- 0 1 2 3 4
4.5 LOOP循环 #
sql
DELIMITER //
CREATE PROCEDURE loop_example(IN max_count INT)
BEGIN
DECLARE i INT DEFAULT 0;
DECLARE result VARCHAR(1000) DEFAULT '';
my_loop: LOOP
SET result = CONCAT(result, i, ' ');
SET i = i + 1;
IF i >= max_count THEN
LEAVE my_loop;
END IF;
END LOOP my_loop;
SELECT result;
END //
DELIMITER ;
CALL loop_example(5); -- 0 1 2 3 4
4.6 ITERATE语句 #
sql
DELIMITER //
CREATE PROCEDURE iterate_example(IN max_count INT)
BEGIN
DECLARE i INT DEFAULT 0;
DECLARE result VARCHAR(1000) DEFAULT '';
my_loop: LOOP
SET i = i + 1;
-- 跳过偶数
IF i % 2 = 0 THEN
ITERATE my_loop;
END IF;
SET result = CONCAT(result, i, ' ');
IF i >= max_count THEN
LEAVE my_loop;
END IF;
END LOOP my_loop;
SELECT result;
END //
DELIMITER ;
CALL iterate_example(10); -- 1 3 5 7 9
五、游标 #
5.1 游标基本用法 #
sql
DELIMITER //
CREATE PROCEDURE cursor_example()
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE v_id INT;
DECLARE v_name VARCHAR(50);
DECLARE v_email VARCHAR(100);
-- 声明游标
DECLARE cur CURSOR FOR SELECT id, name, email FROM users;
-- 声明结束处理程序
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
-- 创建临时表存储结果
CREATE TEMPORARY TABLE IF NOT EXISTS temp_results (
id INT,
name VARCHAR(50),
email VARCHAR(100)
);
-- 打开游标
OPEN cur;
-- 循环读取
read_loop: LOOP
FETCH cur INTO v_id, v_name, v_email;
IF done THEN
LEAVE read_loop;
END IF;
-- 处理数据
INSERT INTO temp_results VALUES (v_id, UPPER(v_name), LOWER(v_email));
END LOOP;
-- 关闭游标
CLOSE cur;
-- 返回结果
SELECT * FROM temp_results;
DROP TEMPORARY TABLE temp_results;
END //
DELIMITER ;
5.2 游标与条件处理 #
sql
DELIMITER //
CREATE PROCEDURE cursor_with_conditions()
BEGIN
DECLARE v_id INT;
DECLARE v_balance DECIMAL(10,2);
DECLARE v_total DECIMAL(10,2) DEFAULT 0;
DECLARE done INT DEFAULT FALSE;
DECLARE cur CURSOR FOR SELECT id, balance FROM users;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cur;
process_loop: LOOP
FETCH cur INTO v_id, v_balance;
IF done THEN
LEAVE process_loop;
END IF;
-- 累加余额
SET v_total = v_total + COALESCE(v_balance, 0);
END LOOP;
CLOSE cur;
SELECT v_total AS total_balance;
END //
DELIMITER ;
六、错误处理 #
6.1 DECLARE HANDLER #
sql
DELIMITER //
CREATE PROCEDURE handler_example()
BEGIN
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION
BEGIN
GET DIAGNOSTICS CONDITION 1
@sqlstate = RETURNED_SQLSTATE,
@errno = MYSQL_ERRNO,
@text = MESSAGE_TEXT;
SELECT @sqlstate, @errno, @text AS error;
END;
-- 故意产生错误
INSERT INTO non_existent_table VALUES (1);
SELECT 'Continue after error' AS result;
END //
DELIMITER ;
6.2 处理特定错误 #
sql
DELIMITER //
CREATE PROCEDURE insert_user(IN p_name VARCHAR(50), IN p_email VARCHAR(100))
BEGIN
DECLARE EXIT HANDLER FOR 1062 -- 重复键错误
BEGIN
SELECT 'Duplicate email' AS error;
END;
INSERT INTO users (name, email) VALUES (p_name, p_email);
SELECT 'User inserted' AS result;
END //
DELIMITER ;
6.3 事务错误处理 #
sql
DELIMITER //
CREATE PROCEDURE transfer_money(
IN from_user INT,
IN to_user INT,
IN amount DECIMAL(10,2)
)
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
SELECT 'Transfer failed' AS result;
END;
START TRANSACTION;
UPDATE users SET balance = balance - amount WHERE id = from_user;
UPDATE users SET balance = balance + amount WHERE id = to_user;
COMMIT;
SELECT 'Transfer successful' AS result;
END //
DELIMITER ;
七、动态SQL #
7.1 PREPARE和EXECUTE #
sql
DELIMITER //
CREATE PROCEDURE dynamic_query(IN table_name VARCHAR(50))
BEGIN
SET @sql = CONCAT('SELECT * FROM ', table_name);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END //
DELIMITER ;
CALL dynamic_query('users');
7.2 带参数的动态SQL #
sql
DELIMITER //
CREATE PROCEDURE dynamic_where(
IN table_name VARCHAR(50),
IN column_name VARCHAR(50),
IN value VARCHAR(100)
)
BEGIN
SET @sql = CONCAT('SELECT * FROM ', table_name, ' WHERE ', column_name, ' = ?');
SET @value = value;
PREPARE stmt FROM @sql;
EXECUTE stmt USING @value;
DEALLOCATE PREPARE stmt;
END //
DELIMITER ;
CALL dynamic_where('users', 'name', 'John');
八、存储过程管理 #
8.1 查看存储过程 #
sql
-- 查看存储过程列表
SHOW PROCEDURE STATUS WHERE Db = 'mydb';
-- 查看存储过程定义
SHOW CREATE PROCEDURE get_user_by_id;
-- 从information_schema查询
SELECT
routine_name,
routine_type,
data_type,
routine_definition
FROM information_schema.routines
WHERE routine_schema = 'mydb';
8.2 修改存储过程 #
sql
-- MariaDB不支持ALTER PROCEDURE修改内容
-- 需要删除后重新创建
DROP PROCEDURE IF EXISTS get_user_by_id;
DELIMITER //
CREATE PROCEDURE get_user_by_id(IN user_id INT)
BEGIN
SELECT * FROM users WHERE id = user_id;
END //
DELIMITER ;
8.3 删除存储过程 #
sql
-- 删除存储过程
DROP PROCEDURE get_user_by_id;
-- 如果存在则删除
DROP PROCEDURE IF EXISTS get_user_by_id;
九、存储函数 #
9.1 创建存储函数 #
sql
DELIMITER //
CREATE FUNCTION get_user_name(user_id INT)
RETURNS VARCHAR(50)
DETERMINISTIC
READS SQL DATA
BEGIN
DECLARE v_name VARCHAR(50);
SELECT name INTO v_name FROM users WHERE id = user_id;
RETURN v_name;
END //
DELIMITER ;
-- 使用函数
SELECT get_user_name(1);
SELECT id, get_user_name(id) AS name FROM orders;
9.2 函数特性 #
sql
DELIMITER //
CREATE FUNCTION calculate_tax(amount DECIMAL(10,2), rate DECIMAL(5,4))
RETURNS DECIMAL(10,2)
DETERMINISTIC -- 相同输入返回相同结果
NO SQL -- 不包含SQL语句
BEGIN
RETURN amount * rate;
END //
DELIMITER ;
-- 函数特性说明
-- DETERMINISTIC: 确定性函数
-- NOT DETERMINISTIC: 非确定性函数(默认)
-- CONTAINS SQL: 包含SQL语句
-- NO SQL: 不包含SQL语句
-- READS SQL DATA: 只读SQL
-- MODIFIES SQL DATA: 修改数据
十、最佳实践 #
10.1 命名规范 #
sql
-- 存储过程命名
-- sp_ 或 proc_ 前缀
CREATE PROCEDURE sp_get_user_by_id(...);
CREATE PROCEDURE proc_calculate_total(...);
-- 函数命名
-- fn_ 或 func_ 前缀
CREATE FUNCTION fn_get_user_name(...);
10.2 错误处理 #
sql
DELIMITER //
CREATE PROCEDURE safe_insert_user(
IN p_name VARCHAR(50),
IN p_email VARCHAR(100),
OUT p_result VARCHAR(100)
)
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
GET DIAGNOSTICS CONDITION 1
@text = MESSAGE_TEXT;
SET p_result = CONCAT('Error: ', @text);
ROLLBACK;
END;
START TRANSACTION;
-- 验证输入
IF p_name IS NULL OR p_name = '' THEN
SET p_result = 'Error: Name is required';
ROLLBACK;
ELSE
INSERT INTO users (name, email) VALUES (p_name, p_email);
SET p_result = 'Success';
COMMIT;
END IF;
END //
DELIMITER ;
10.3 性能建议 #
| 建议 | 说明 |
|---|---|
| 减少网络往返 | 批量操作放在存储过程中 |
| 使用索引 | 确保查询使用索引 |
| 避免游标 | 尽量使用集合操作 |
| 事务控制 | 合理使用事务 |
| 错误处理 | 添加适当的错误处理 |
十一、总结 #
存储过程要点:
| 操作 | 语法 |
|---|---|
| 创建 | CREATE PROCEDURE |
| 调用 | CALL procedure_name() |
| 查看 | SHOW CREATE PROCEDURE |
| 删除 | DROP PROCEDURE |
参数类型:
| 类型 | 说明 |
|---|---|
| IN | 输入参数(默认) |
| OUT | 输出参数 |
| INOUT | 输入输出参数 |
最佳实践:
- 合理使用存储过程封装业务逻辑
- 添加适当的错误处理
- 注意性能优化
- 保持代码简洁可读
- 添加注释说明
下一步,让我们学习事务!
最后更新:2026-03-27