存储过程:预编译的SQL语句集合,存储在数据库中,可重复调用。
东巴文理解:
存储过程 = 预制菜
提前准备好的菜品
需要时直接加热食用
存储过程提前编译,调用时直接执行
优点:
缺点:
语法格式:
CREATE PROCEDURE 存储过程名([参数列表])
BEGIN
SQL语句;
END;
示例:
-- 修改分隔符
DELIMITER //
-- 创建存储过程
CREATE PROCEDURE get_all_users()
BEGIN
SELECT * FROM users;
END //
-- 恢复分隔符
DELIMITER ;
-- 调用存储过程
CALL get_all_users();
IN参数:输入参数,调用时传入值。
示例:
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);
OUT参数:输出参数,返回值给调用者。
示例:
DELIMITER //
CREATE PROCEDURE get_user_count(OUT total_count INT)
BEGIN
SELECT COUNT(*) INTO total_count FROM users;
END //
DELIMITER ;
-- 调用
CALL get_user_count(@count);
SELECT @count;
INOUT参数:既可输入也可输出。
示例:
DELIMITER //
CREATE PROCEDURE double_number(INOUT num INT)
BEGIN
SET num = num * 2;
END //
DELIMITER ;
-- 调用
SET @number = 10;
CALL double_number(@number);
SELECT @number; -- 输出20
语法格式:
CALL 存储过程名([参数列表]);
示例:
-- 无参数
CALL get_all_users();
-- IN参数
CALL get_user_by_id(1);
-- OUT参数
CALL get_user_count(@count);
SELECT @count;
-- INOUT参数
SET @number = 10;
CALL double_number(@number);
SELECT @number;
声明变量:
DECLARE 变量名 数据类型 [DEFAULT 默认值];
示例:
DELIMITER //
CREATE PROCEDURE test_variables()
BEGIN
-- 声明变量
DECLARE name VARCHAR(50) DEFAULT '张三';
DECLARE age INT DEFAULT 18;
-- 赋值
SET name = '李四';
SET age = 20;
-- 使用变量
SELECT name, age;
END //
DELIMITER ;
CALL test_variables();
用户变量:以@开头,连接期间有效。
示例:
-- 设置用户变量
SET @name = '王五';
SET @age = 25;
-- 使用用户变量
SELECT @name, @age;
-- 在存储过程中使用
DELIMITER //
CREATE PROCEDURE use_user_variable()
BEGIN
SELECT @name, @age;
END //
DELIMITER ;
CALL use_user_variable();
系统变量:MySQL服务器变量。
示例:
-- 查看系统变量
SHOW VARIABLES LIKE 'max_connections';
-- 修改系统变量(会话级别)
SET SESSION sql_mode = 'STRICT_TRANS_TABLES';
-- 修改系统变量(全局级别)
SET GLOBAL max_connections = 1000;
语法格式:
IF 条件 THEN
语句;
ELSEIF 条件 THEN
语句;
ELSE
语句;
END IF;
示例:
DELIMITER //
CREATE PROCEDURE get_grade(IN score INT, OUT grade VARCHAR(10))
BEGIN
IF score >= 90 THEN
SET grade = '优秀';
ELSEIF score >= 80 THEN
SET grade = '良好';
ELSEIF score >= 70 THEN
SET grade = '中等';
ELSEIF score >= 60 THEN
SET grade = '及格';
ELSE
SET grade = '不及格';
END IF;
END //
DELIMITER ;
-- 调用
CALL get_grade(85, @grade);
SELECT @grade; -- 输出:良好
语法格式:
CASE 表达式
WHEN 值1 THEN 语句1
WHEN 值2 THEN 语句2
ELSE 语句3
END CASE;
示例:
DELIMITER //
CREATE PROCEDURE get_month_name(IN month_num INT, OUT month_name VARCHAR(20))
BEGIN
CASE month_num
WHEN 1 THEN SET month_name = '一月';
WHEN 2 THEN SET month_name = '二月';
WHEN 3 THEN SET month_name = '三月';
WHEN 4 THEN SET month_name = '四月';
WHEN 5 THEN SET month_name = '五月';
WHEN 6 THEN SET month_name = '六月';
WHEN 7 THEN SET month_name = '七月';
WHEN 8 THEN SET month_name = '八月';
WHEN 9 THEN SET month_name = '九月';
WHEN 10 THEN SET month_name = '十月';
WHEN 11 THEN SET month_name = '十一月';
WHEN 12 THEN SET month_name = '十二月';
ELSE SET month_name = '无效月份';
END CASE;
END //
DELIMITER ;
-- 调用
CALL get_month_name(5, @name);
SELECT @name; -- 输出:五月
语法格式:
WHILE 条件 DO
语句;
END WHILE;
示例:
DELIMITER //
CREATE PROCEDURE sum_numbers(IN n INT, OUT total INT)
BEGIN
DECLARE i INT DEFAULT 1;
SET total = 0;
WHILE i <= n DO
SET total = total + i;
SET i = i + 1;
END WHILE;
END //
DELIMITER ;
-- 调用
CALL sum_numbers(100, @sum);
SELECT @sum; -- 输出:5050
语法格式:
REPEAT
语句;
UNTIL 条件 END REPEAT;
示例:
DELIMITER //
CREATE PROCEDURE sum_numbers_repeat(IN n INT, OUT total INT)
BEGIN
DECLARE i INT DEFAULT 1;
SET total = 0;
REPEAT
SET total = total + i;
SET i = i + 1;
UNTIL i > n END REPEAT;
END //
DELIMITER ;
-- 调用
CALL sum_numbers_repeat(100, @sum);
SELECT @sum; -- 输出:5050
语法格式:
循环标签: LOOP
语句;
IF 条件 THEN
LEAVE 循环标签;
END IF;
END LOOP 循环标签;
示例:
DELIMITER //
CREATE PROCEDURE sum_numbers_loop(IN n INT, OUT total INT)
BEGIN
DECLARE i INT DEFAULT 1;
SET total = 0;
sum_loop: LOOP
SET total = total + i;
SET i = i + 1;
IF i > n THEN
LEAVE sum_loop;
END IF;
END LOOP sum_loop;
END //
DELIMITER ;
-- 调用
CALL sum_numbers_loop(100, @sum);
SELECT @sum; -- 输出:5050
ITERATE:跳过当前循环,继续下一次循环。
示例:
DELIMITER //
CREATE PROCEDURE sum_even_numbers(IN n INT, OUT total INT)
BEGIN
DECLARE i INT DEFAULT 0;
SET total = 0;
sum_loop: LOOP
SET i = i + 1;
IF i > n THEN
LEAVE sum_loop;
END IF;
-- 跳过奇数
IF i % 2 = 1 THEN
ITERATE sum_loop;
END IF;
SET total = total + i;
END LOOP sum_loop;
END //
DELIMITER ;
-- 调用
CALL sum_even_numbers(10, @sum);
SELECT @sum; -- 输出:30 (2+4+6+8+10)
游标:用于遍历查询结果集的数据库对象。
东巴文理解:
游标 = 书签
看书时用书签标记位置
游标标记结果集的位置
逐行读取数据
语法格式:
DECLARE 游标名 CURSOR FOR SELECT语句;
语法格式:
OPEN 游标名;
语法格式:
FETCH 游标名 INTO 变量列表;
语法格式:
CLOSE 游标名;
DELIMITER //
CREATE PROCEDURE process_users()
BEGIN
-- 声明变量
DECLARE done INT DEFAULT FALSE;
DECLARE user_id INT;
DECLARE user_name VARCHAR(50);
-- 声明游标
DECLARE user_cursor CURSOR FOR
SELECT id, name FROM users;
-- 声明结束处理程序
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
-- 打开游标
OPEN user_cursor;
-- 循环读取
read_loop: LOOP
FETCH user_cursor INTO user_id, user_name;
IF done THEN
LEAVE read_loop;
END IF;
-- 处理数据
SELECT user_id, user_name;
END LOOP;
-- 关闭游标
CLOSE user_cursor;
END //
DELIMITER ;
CALL process_users();
DELIMITER //
CREATE PROCEDURE calculate_statistics()
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE dept_id INT;
DECLARE dept_name VARCHAR(50);
DECLARE emp_count INT;
DECLARE total_salary DECIMAL(10, 2);
DECLARE dept_cursor CURSOR FOR
SELECT id, dept_name FROM departments;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
-- 创建临时表存储结果
CREATE TEMPORARY TABLE IF NOT EXISTS dept_stats (
dept_id INT,
dept_name VARCHAR(50),
employee_count INT,
total_salary DECIMAL(10, 2)
);
TRUNCATE TABLE dept_stats;
OPEN dept_cursor;
stats_loop: LOOP
FETCH dept_cursor INTO dept_id, dept_name;
IF done THEN
LEAVE stats_loop;
END IF;
-- 计算统计信息
SELECT COUNT(*), IFNULL(SUM(salary), 0)
INTO emp_count, total_salary
FROM employees
WHERE dept_id = dept_id;
-- 插入结果
INSERT INTO dept_stats VALUES (dept_id, dept_name, emp_count, total_salary);
END LOOP;
CLOSE dept_cursor;
-- 返回结果
SELECT * FROM dept_stats;
DROP TEMPORARY TABLE IF EXISTS dept_stats;
END //
DELIMITER ;
CALL calculate_statistics();
语法格式:
DECLARE {CONTINUE|EXIT} HANDLER FOR
{错误代码|SQLSTATE值|条件名}
处理语句;
参数说明:
示例:
DELIMITER //
CREATE PROCEDURE safe_insert_user(
IN user_name VARCHAR(50),
IN user_email VARCHAR(100)
)
BEGIN
-- 声明错误处理程序
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
-- 回滚事务
ROLLBACK;
-- 返回错误信息
SELECT '插入失败' AS result;
END;
-- 开始事务
START TRANSACTION;
-- 插入数据
INSERT INTO users (name, email) VALUES (user_name, user_email);
-- 提交事务
COMMIT;
SELECT '插入成功' AS result;
END //
DELIMITER ;
-- 调用
CALL safe_insert_user('张三', 'zhangsan@example.com');
语法格式:
SIGNAL SQLSTATE '状态码'
SET MESSAGE_TEXT = '错误信息';
示例:
DELIMITER //
CREATE PROCEDURE withdraw_money(
IN user_id INT,
IN amount DECIMAL(10, 2)
)
BEGIN
DECLARE current_balance DECIMAL(10, 2);
-- 查询当前余额
SELECT balance INTO current_balance
FROM accounts
WHERE id = user_id;
-- 检查余额是否足够
IF current_balance < amount THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '余额不足';
END IF;
-- 扣款
UPDATE accounts
SET balance = balance - amount
WHERE id = user_id;
SELECT '扣款成功' AS result;
END //
DELIMITER ;
-- 调用
CALL withdraw_money(1, 1000);
存储函数:返回单个值的存储过程。
与存储过程的区别:
| 特性 | 存储过程 | 存储函数 |
|---|---|---|
| 返回值 | 可以返回多个值 | 必须返回一个值 |
| 参数 | IN/OUT/INOUT | 只有IN |
| 调用方式 | CALL语句 | SELECT语句 |
| SQL语句 | 可以包含所有SQL | 不能包含动态SQL |
语法格式:
CREATE FUNCTION 函数名([参数列表])
RETURNS 返回类型
[DETERMINISTIC|NOT DETERMINISTIC]
BEGIN
SQL语句;
RETURN 返回值;
END;
参数说明:
示例1:计算年龄:
DELIMITER //
CREATE FUNCTION calculate_age(birth_date DATE)
RETURNS INT
DETERMINISTIC
BEGIN
DECLARE age INT;
SET age = TIMESTAMPDIFF(YEAR, birth_date, CURDATE());
RETURN age;
END //
DELIMITER ;
-- 调用
SELECT calculate_age('1990-05-15');
示例2:格式化字符串:
DELIMITER //
CREATE FUNCTION format_name(first_name VARCHAR(50), last_name VARCHAR(50))
RETURNS VARCHAR(100)
DETERMINISTIC
BEGIN
RETURN CONCAT(last_name, ' ', first_name);
END //
DELIMITER ;
-- 调用
SELECT format_name('三', '张');
示例3:计算折扣价:
DELIMITER //
CREATE FUNCTION calculate_discount_price(
original_price DECIMAL(10, 2),
discount_rate DECIMAL(5, 2)
)
RETURNS DECIMAL(10, 2)
DETERMINISTIC
BEGIN
DECLARE discount_price DECIMAL(10, 2);
SET discount_price = original_price * (1 - discount_rate / 100);
RETURN discount_price;
END //
DELIMITER ;
-- 调用
SELECT calculate_discount_price(100.00, 20);
-- 创建员工表
CREATE TABLE employees (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50),
birth_date DATE,
salary DECIMAL(10, 2)
);
-- 插入数据
INSERT INTO employees (name, birth_date, salary) VALUES
('张三', '1990-05-15', 10000),
('李四', '1985-08-20', 15000),
('王五', '1995-03-10', 8000);
-- 在查询中使用函数
SELECT
name,
birth_date,
calculate_age(birth_date) AS age,
salary,
calculate_discount_price(salary, 10) AS tax
FROM employees;
-- 查看存储过程列表
SHOW PROCEDURE STATUS;
-- 查看指定数据库的存储过程
SHOW PROCEDURE STATUS WHERE Db = 'database_name';
-- 模糊查询
SHOW PROCEDURE STATUS LIKE 'get_%';
-- 查看存储函数列表
SHOW FUNCTION STATUS;
-- 查看指定数据库的存储函数
SHOW FUNCTION STATUS WHERE Db = 'database_name';
-- 查看存储过程定义
SHOW CREATE PROCEDURE get_user_by_id;
-- 查看存储函数定义
SHOW CREATE FUNCTION calculate_age;
-- 从information_schema查询
SELECT * FROM information_schema.ROUTINES
WHERE ROUTINE_NAME = 'get_user_by_id';
MySQL不支持直接修改存储过程和函数,需要先删除再创建:
-- 删除存储过程
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 ;
语法格式:
DROP PROCEDURE [IF EXISTS] 存储过程名;
DROP FUNCTION [IF EXISTS] 函数名;
示例:
-- 删除存储过程
DROP PROCEDURE IF EXISTS get_user_by_id;
-- 删除存储函数
DROP FUNCTION IF EXISTS calculate_age;
DELIMITER //
CREATE PROCEDURE paginate_users(
IN page_num INT,
IN page_size INT,
OUT total_count INT,
OUT total_pages INT
)
BEGIN
DECLARE offset_num INT;
-- 计算偏移量
SET offset_num = (page_num - 1) * page_size;
-- 查询总数
SELECT COUNT(*) INTO total_count FROM users;
-- 计算总页数
SET total_pages = CEIL(total_count / page_size);
-- 分页查询
SELECT * FROM users
LIMIT offset_num, page_size;
END //
DELIMITER ;
-- 调用
CALL paginate_users(1, 10, @total, @pages);
SELECT @total, @pages;
DELIMITER //
CREATE PROCEDURE batch_insert_users(
IN user_count INT
)
BEGIN
DECLARE i INT DEFAULT 1;
DECLARE user_name VARCHAR(50);
WHILE i <= user_count DO
SET user_name = CONCAT('用户', i);
INSERT INTO users (name, email, created_at)
VALUES (user_name, CONCAT(user_name, '@example.com'), NOW());
SET i = i + 1;
END WHILE;
SELECT CONCAT('成功插入 ', user_count, ' 条记录') AS result;
END //
DELIMITER ;
-- 调用
CALL batch_insert_users(100);
DELIMITER //
CREATE PROCEDURE migrate_data()
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE user_id INT;
DECLARE user_name VARCHAR(50);
DECLARE user_email VARCHAR(100);
DECLARE user_cursor CURSOR FOR
SELECT id, name, email FROM old_users;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
-- 开始事务
START TRANSACTION;
OPEN user_cursor;
migrate_loop: LOOP
FETCH user_cursor INTO user_id, user_name, user_email;
IF done THEN
LEAVE migrate_loop;
END IF;
-- 插入新表
INSERT INTO users (name, email, created_at)
VALUES (user_name, user_email, NOW())
ON DUPLICATE KEY UPDATE email = user_email;
END LOOP;
CLOSE user_cursor;
-- 提交事务
COMMIT;
SELECT '数据迁移完成' AS result;
END //
DELIMITER ;
-- 调用
CALL migrate_data();
✅ 存储过程是预编译的SQL语句集合 ✅ 参数类型有IN、OUT、INOUT ✅ 变量有局部变量、用户变量、系统变量 ✅ 流程控制有IF、CASE、WHILE、REPEAT、LOOP ✅ 游标用于遍历结果集 ✅ 存储函数必须返回一个值
完成本章学习后,请确认您能够: