1. 存储过程基础概念与核心价值
存储过程(Stored Procedure)是MySQL数据库中一组预编译的SQL语句集合,它像脚本一样存储在数据库服务器端,通过名称调用执行。我第一次接触存储过程是在处理电商平台的订单报表时,当时需要每天凌晨3点统计前一天的销售数据,手动执行SQL既繁琐又容易出错。存储过程帮我解决了这个痛点,它让我能够把复杂的业务逻辑封装在数据库层。
存储过程的核心优势体现在三个方面:
- 执行效率:预编译特性使得重复调用时无需重复解析和优化
- 代码复用:一次创建多次调用,减少网络传输开销
- 安全控制:通过权限管理实现细粒度的数据访问控制
在企业级应用中,存储过程特别适合处理以下场景:
- 定期执行的报表统计任务
- 需要事务保证的多表操作
- 包含复杂业务规则的数据处理
2. 存储过程创建与基础语法详解
2.1 创建语法结构解析
创建存储过程的基本语法模板如下:
sql复制DELIMITER //
CREATE PROCEDURE procedure_name([参数列表])
[特性声明]
BEGIN
-- 过程体
END //
DELIMITER ;
这里有几个关键点需要注意:
- DELIMITER重定义:因为过程体内包含分号,需要临时修改分隔符
- 参数模式:支持IN(输入)、OUT(输出)、INOUT(输入输出)三种模式
- 特性声明:包括COMMENT注释、LANGUAGE SQL等特性
实际创建示例:
sql复制DELIMITER //
CREATE PROCEDURE GetCustomerOrders(IN cust_id INT)
BEGIN
SELECT * FROM orders WHERE customer_id = cust_id;
END //
DELIMITER ;
2.2 参数传递机制
存储过程的参数处理是初学者最容易混淆的部分。通过一个订单统计案例来说明:
sql复制CREATE PROCEDURE GetOrderStats(
IN p_customer_id INT,
OUT p_order_count INT,
OUT p_total_amount DECIMAL(10,2)
)
BEGIN
SELECT COUNT(*), SUM(amount)
INTO p_order_count, p_total_amount
FROM orders
WHERE customer_id = p_customer_id;
END
调用这个存储过程时:
sql复制CALL GetOrderStats(123, @count, @total);
SELECT @count, @total; -- 查看输出参数
重要提示:MySQL中所有变量(包括参数)都是会话级别的,使用@符号定义的变量在会话结束后会自动销毁
3. 流程控制与错误处理
3.1 条件分支实现业务逻辑
存储过程中常用的条件语句包括IF和CASE:
sql复制CREATE PROCEDURE CheckInventory(
IN product_id INT,
IN required_qty INT,
OUT status VARCHAR(20)
)
BEGIN
DECLARE available_qty INT;
SELECT quantity INTO available_qty
FROM inventory WHERE product_id = product_id;
IF available_qty >= required_qty THEN
SET status = 'In Stock';
ELSEIF available_qty > 0 THEN
SET status = 'Low Stock';
ELSE
SET status = 'Out of Stock';
END IF;
END
3.2 循环处理数据集
处理需要遍历数据的情况时,MySQL提供了三种循环结构:
- WHILE循环:
sql复制WHILE condition DO
-- 循环体
END WHILE;
- REPEAT循环:
sql复制REPEAT
-- 循环体
UNTIL condition END REPEAT;
- LOOP循环:
sql复制[begin_label:] LOOP
-- 循环体
IF condition THEN
LEAVE begin_label;
END IF;
END LOOP begin_label;
实际案例:批量生成测试数据
sql复制CREATE PROCEDURE GenerateTestData(IN num_records INT)
BEGIN
DECLARE i INT DEFAULT 1;
WHILE i <= num_records DO
INSERT INTO test_data VALUES (i, CONCAT('Item-', i));
SET i = i + 1;
END WHILE;
END
3.3 异常处理机制
健壮的存储过程必须包含错误处理。MySQL使用DECLARE HANDLER语句:
sql复制CREATE PROCEDURE SafeTransfer(
IN from_acc INT,
IN to_acc INT,
IN amount DECIMAL(10,2)
)
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
SELECT 'Transaction failed' AS result;
END;
START TRANSACTION;
UPDATE accounts SET balance = balance - amount WHERE id = from_acc;
UPDATE accounts SET balance = balance + amount WHERE id = to_acc;
COMMIT;
SELECT 'Transaction completed' AS result;
END
4. 存储过程高级特性与应用
4.1 动态SQL执行
当需要根据参数动态构建SQL时,可以使用预处理语句:
sql复制CREATE PROCEDURE DynamicQuery(
IN table_name VARCHAR(50),
IN condition VARCHAR(100)
)
BEGIN
SET @sql = CONCAT('SELECT * FROM ', table_name, ' WHERE ', condition);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END
安全提示:动态SQL必须注意防范SQL注入,建议对输入参数进行严格校验
4.2 游标使用详解
游标(Cursor)允许逐行处理查询结果,典型应用场景包括数据迁移和复杂计算:
sql复制CREATE PROCEDURE ProcessOrders()
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE order_id INT;
DECLARE order_amount DECIMAL(10,2);
DECLARE cur CURSOR FOR
SELECT id, amount FROM orders WHERE status = 'pending';
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cur;
read_loop: LOOP
FETCH cur INTO order_id, order_amount;
IF done THEN
LEAVE read_loop;
END IF;
-- 处理逻辑
IF order_amount > 1000 THEN
UPDATE orders SET priority = 'high' WHERE id = order_id;
END IF;
END LOOP;
CLOSE cur;
END
4.3 临时表应用
对于复杂的数据处理,临时表能显著提高性能:
sql复制CREATE PROCEDURE GenerateMonthlyReport(IN month INT, IN year INT)
BEGIN
-- 创建临时表
CREATE TEMPORARY TABLE IF NOT EXISTS temp_sales (
product_id INT,
total_sales DECIMAL(12,2),
sale_count INT
);
-- 计算数据
INSERT INTO temp_sales
SELECT product_id, SUM(amount), COUNT(*)
FROM orders
WHERE MONTH(order_date) = month AND YEAR(order_date) = year
GROUP BY product_id;
-- 使用临时表生成报表
SELECT p.name, s.total_sales, s.sale_count
FROM temp_sales s
JOIN products p ON s.product_id = p.id
ORDER BY s.total_sales DESC;
-- 清理
DROP TEMPORARY TABLE IF EXISTS temp_sales;
END
5. 性能优化与最佳实践
5.1 执行计划分析
使用EXPLAIN分析存储过程中的SQL性能:
sql复制CREATE PROCEDURE OptimizedQuery(IN category_id INT)
BEGIN
EXPLAIN SELECT p.* FROM products p
JOIN categories c ON p.category_id = c.id
WHERE c.id = category_id;
-- 实际查询
SELECT p.* FROM products p
JOIN categories c ON p.category_id = c.id
WHERE c.id = category_id;
END
5.2 变量使用技巧
合理使用变量能提升存储过程性能:
sql复制CREATE PROCEDURE CalculateOrderTotal(IN order_id INT)
BEGIN
DECLARE subtotal DECIMAL(10,2);
DECLARE tax_rate DECIMAL(5,2) DEFAULT 0.08;
DECLARE shipping_cost DECIMAL(6,2) DEFAULT 5.00;
-- 计算小计
SELECT SUM(price * quantity) INTO subtotal
FROM order_items
WHERE order_id = order_id;
-- 计算总计
SELECT
subtotal,
subtotal * tax_rate AS tax,
shipping_cost,
subtotal + (subtotal * tax_rate) + shipping_cost AS total;
END
5.3 存储过程维护建议
- 版本控制:将存储过程脚本纳入代码版本管理系统
- 文档注释:使用标准格式注释:
sql复制CREATE PROCEDURE GetCustomerDetails(
IN cust_id INT
)
COMMENT '获取客户详细信息包括订单历史'
BEGIN
-- 过程体
END
- 定期审查:使用SHOW PROCEDURE STATUS查看所有存储过程
- 依赖分析:通过information_schema.routines表分析依赖关系
6. 实战案例:电商订单处理系统
6.1 订单创建流程
sql复制CREATE PROCEDURE CreateOrder(
IN customer_id INT,
IN product_list JSON, -- 格式:[{"id":1,"qty":2},{"id":3,"qty":1}]
OUT order_id INT
)
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
RESIGNAL;
END;
START TRANSACTION;
-- 创建订单主记录
INSERT INTO orders (customer_id, order_date, status)
VALUES (customer_id, NOW(), 'pending');
SET order_id = LAST_INSERT_ID();
-- 处理订单项
SET @i = 0;
SET @item_count = JSON_LENGTH(product_list);
WHILE @i < @item_count DO
SET @product_id = JSON_EXTRACT(product_list, CONCAT('$[', @i, '].id'));
SET @quantity = JSON_EXTRACT(product_list, CONCAT('$[', @i, '].qty'));
-- 获取产品价格
SELECT price INTO @price FROM products WHERE id = @product_id;
-- 添加订单项
INSERT INTO order_items (order_id, product_id, quantity, price)
VALUES (order_id, @product_id, @quantity, @price);
-- 更新库存
UPDATE inventory
SET quantity = quantity - @quantity
WHERE product_id = @product_id;
SET @i = @i + 1;
END WHILE;
COMMIT;
END
6.2 库存预警处理
sql复制CREATE PROCEDURE CheckInventoryLevels()
BEGIN
-- 创建临时表存储低库存产品
CREATE TEMPORARY TABLE IF NOT EXISTS low_inventory (
product_id INT PRIMARY KEY,
product_name VARCHAR(100),
current_qty INT,
min_required INT
);
-- 找出库存不足的产品
INSERT INTO low_inventory
SELECT p.id, p.name, i.quantity, p.min_stock
FROM products p
JOIN inventory i ON p.id = i.product_id
WHERE i.quantity < p.min_stock;
-- 生成采购建议
SELECT
li.product_id,
li.product_name,
li.current_qty AS current_inventory,
li.min_required AS minimum_required,
(li.min_required * 2 - li.current_qty) AS suggested_purchase,
v.name AS preferred_vendor,
v.contact_email
FROM low_inventory li
LEFT JOIN product_vendors pv ON li.product_id = pv.product_id
LEFT JOIN vendors v ON pv.vendor_id = v.id
WHERE pv.is_primary = 1;
-- 清理
DROP TEMPORARY TABLE IF EXISTS low_inventory;
END
6.3 销售数据分析
sql复制CREATE PROCEDURE GenerateSalesReport(
IN start_date DATE,
IN end_date DATE,
IN group_by VARCHAR(20) -- 'day', 'week', 'month', 'product', 'category'
)
BEGIN
CASE group_by
WHEN 'day' THEN
SELECT
DATE(order_date) AS period,
COUNT(*) AS order_count,
SUM(amount) AS total_sales
FROM orders
WHERE order_date BETWEEN start_date AND end_date
GROUP BY DATE(order_date)
ORDER BY DATE(order_date);
WHEN 'week' THEN
SELECT
YEARWEEK(order_date) AS period,
COUNT(*) AS order_count,
SUM(amount) AS total_sales
FROM orders
WHERE order_date BETWEEN start_date AND end_date
GROUP BY YEARWEEK(order_date)
ORDER BY YEARWEEK(order_date);
WHEN 'month' THEN
SELECT
DATE_FORMAT(order_date, '%Y-%m') AS period,
COUNT(*) AS order_count,
SUM(amount) AS total_sales
FROM orders
WHERE order_date BETWEEN start_date AND end_date
GROUP BY DATE_FORMAT(order_date, '%Y-%m')
ORDER BY DATE_FORMAT(order_date, '%Y-%m');
WHEN 'product' THEN
SELECT
p.id AS product_id,
p.name AS product_name,
COUNT(*) AS order_count,
SUM(oi.quantity) AS total_quantity,
SUM(oi.price * oi.quantity) AS total_sales
FROM order_items oi
JOIN orders o ON oi.order_id = o.id
JOIN products p ON oi.product_id = p.id
WHERE o.order_date BETWEEN start_date AND end_date
GROUP BY p.id, p.name
ORDER BY total_sales DESC;
WHEN 'category' THEN
SELECT
c.id AS category_id,
c.name AS category_name,
COUNT(*) AS order_count,
SUM(oi.quantity) AS total_quantity,
SUM(oi.price * oi.quantity) AS total_sales
FROM order_items oi
JOIN orders o ON oi.order_id = o.id
JOIN products p ON oi.product_id = p.id
JOIN categories c ON p.category_id = c.id
WHERE o.order_date BETWEEN start_date AND end_date
GROUP BY c.id, c.name
ORDER BY total_sales DESC;
ELSE
SELECT 'Invalid group_by parameter' AS error;
END CASE;
END
7. 调试与问题排查
7.1 日志记录技巧
在存储过程中添加调试信息:
sql复制CREATE PROCEDURE DebugExample(IN input_param INT)
BEGIN
-- 创建临时调试表
CREATE TEMPORARY TABLE IF NOT EXISTS debug_log (
seq INT AUTO_INCREMENT PRIMARY KEY,
log_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
message VARCHAR(255)
);
-- 记录开始
INSERT INTO debug_log (message) VALUES (CONCAT('Procedure started with input: ', input_param));
-- 业务逻辑
IF input_param > 100 THEN
INSERT INTO debug_log (message) VALUES ('Input is greater than 100');
-- 处理逻辑...
ELSE
INSERT INTO debug_log (message) VALUES ('Input is 100 or less');
-- 处理逻辑...
END IF;
-- 记录结束
INSERT INTO debug_log (message) VALUES ('Procedure completed');
-- 输出日志
SELECT * FROM debug_log;
-- 清理
DROP TEMPORARY TABLE IF EXISTS debug_log;
END
7.2 常见错误处理
- 权限问题:
sql复制GRANT EXECUTE ON PROCEDURE db_name.procedure_name TO 'user'@'host';
- 变量作用域:
sql复制CREATE PROCEDURE ScopeExample()
BEGIN
DECLARE var1 INT DEFAULT 1; -- 局部变量
BEGIN
DECLARE var1 INT DEFAULT 2; -- 内层作用域变量
SELECT var1; -- 输出2
END;
SELECT var1; -- 输出1
END
- 分号缺失:确保每个语句以分号结束,特别是在使用DELIMITER时
7.3 性能问题排查
使用SHOW PROFILE分析存储过程性能:
sql复制CREATE PROCEDURE ProfileExample()
BEGIN
-- 启用性能分析
SET profiling = 1;
-- 业务逻辑
SELECT * FROM large_table WHERE condition = 1;
-- 查看分析结果
SHOW PROFILE;
-- 关闭分析
SET profiling = 0;
END
8. 存储过程与应用程序集成
8.1 PHP调用示例
php复制<?php
$db = new PDO('mysql:host=localhost;dbname=test', 'user', 'password');
// 调用无参存储过程
$stmt = $db->query("CALL GetRecentOrders()");
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
// 调用带输入参数的存储过程
$stmt = $db->prepare("CALL GetCustomerOrders(:cust_id)");
$stmt->bindValue(':cust_id', 123, PDO::PARAM_INT);
$stmt->execute();
$orders = $stmt->fetchAll();
// 调用带输出参数的存储过程
$stmt = $db->prepare("CALL GetOrderStats(?, @count, @total)");
$stmt->execute([123]);
$stats = $db->query("SELECT @count AS order_count, @total AS total_amount")->fetch();
?>
8.2 Java调用示例
java复制// 使用JDBC调用存储过程
try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS)) {
// 准备调用
String sql = "{call GetOrderStats(?, ?, ?)}";
CallableStatement stmt = conn.prepareCall(sql);
// 设置输入参数
stmt.setInt(1, customerId);
// 注册输出参数
stmt.registerOutParameter(2, Types.INTEGER);
stmt.registerOutParameter(3, Types.DECIMAL);
// 执行
stmt.execute();
// 获取输出参数
int orderCount = stmt.getInt(2);
BigDecimal totalAmount = stmt.getBigDecimal(3);
System.out.println("Orders: " + orderCount + ", Total: " + totalAmount);
}
8.3 Python调用示例
python复制import mysql.connector
def call_procedure():
conn = mysql.connector.connect(
host='localhost',
user='user',
password='password',
database='test'
)
cursor = conn.cursor()
# 调用存储过程
args = (123, 0, 0.0) # (input, output1, output2)
cursor.callproc('GetOrderStats', args)
# 获取结果集
for result in cursor.stored_results():
print(result.fetchall())
# 获取输出参数
cursor.execute("SELECT @_GetOrderStats_1, @_GetOrderStats_2")
outputs = cursor.fetchone()
print(f"Order count: {outputs[0]}, Total amount: {outputs[1]}")
cursor.close()
conn.close()
9. 存储过程设计模式
9.1 工厂模式应用
创建动态执行不同操作的存储过程:
sql复制CREATE PROCEDURE AccountOperationFactory(
IN operation_type VARCHAR(20),
IN account_id INT,
IN amount DECIMAL(10,2)
)
BEGIN
CASE operation_type
WHEN 'deposit' THEN
UPDATE accounts SET balance = balance + amount WHERE id = account_id;
INSERT INTO transactions (account_id, amount, type)
VALUES (account_id, amount, 'deposit');
WHEN 'withdraw' THEN
DECLARE current_balance DECIMAL(10,2);
SELECT balance INTO current_balance FROM accounts WHERE id = account_id;
IF current_balance >= amount THEN
UPDATE accounts SET balance = balance - amount WHERE id = account_id;
INSERT INTO transactions (account_id, amount, type)
VALUES (account_id, amount, 'withdrawal');
ELSE
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Insufficient funds';
END IF;
WHEN 'balance' THEN
SELECT balance FROM accounts WHERE id = account_id;
ELSE
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Invalid operation type';
END CASE;
END
9.2 策略模式实现
根据不同条件应用不同业务规则:
sql复制CREATE PROCEDURE CalculateShipping(
IN order_id INT,
OUT shipping_cost DECIMAL(6,2)
)
BEGIN
DECLARE order_total DECIMAL(10,2);
DECLARE customer_level VARCHAR(20);
DECLARE shipping_zone VARCHAR(10);
-- 获取订单信息
SELECT SUM(price * quantity) INTO order_total
FROM order_items WHERE order_id = order_id;
-- 获取客户等级
SELECT membership_level INTO customer_level
FROM customers c
JOIN orders o ON c.id = o.customer_id
WHERE o.id = order_id;
-- 获取配送区域
SELECT zone INTO shipping_zone
FROM orders o
JOIN addresses a ON o.shipping_address_id = a.id
WHERE o.id = order_id;
-- 应用不同策略
IF customer_level = 'premium' THEN
SET shipping_cost = 0; -- 高级会员免运费
ELSEIF order_total > 100 THEN
SET shipping_cost = 0; -- 满100免运费
ELSEIF shipping_zone = 'local' THEN
SET shipping_cost = 5.00;
ELSEIF shipping_zone = 'remote' THEN
SET shipping_cost = 12.00;
ELSE
SET shipping_cost = 8.00; -- 默认运费
END IF;
END
9.3 观察者模式模拟
通过存储过程实现事件通知机制:
sql复制CREATE PROCEDURE PlaceOrderWithNotifications(
IN customer_id INT,
IN product_list JSON
)
BEGIN
DECLARE new_order_id INT;
DECLARE notification_text VARCHAR(255);
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
RESIGNAL;
END;
START TRANSACTION;
-- 创建订单
CALL CreateOrder(customer_id, product_list, new_order_id);
-- 记录订单创建事件
INSERT INTO event_log (event_type, entity_type, entity_id, details)
VALUES ('order_created', 'order', new_order_id, product_list);
-- 发送邮件通知
SELECT CONCAT('New order #', new_order_id, ' placed') INTO notification_text;
INSERT INTO email_queue (recipient, subject, body)
SELECT email, 'Order Confirmation', notification_text
FROM customers WHERE id = customer_id;
-- 库存检查通知
INSERT INTO inventory_check_queue (order_id)
VALUES (new_order_id);
COMMIT;
END
10. 安全最佳实践
10.1 SQL注入防护
安全参数处理的正确方式:
sql复制CREATE PROCEDURE SafeSearch(
IN search_term VARCHAR(100)
)
BEGIN
-- 安全方式1:参数化查询
SELECT * FROM products
WHERE name LIKE CONCAT('%', search_term, '%');
-- 安全方式2:输入验证
IF search_term REGEXP '[^a-zA-Z0-9 ]' THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Invalid search term';
END IF;
END
10.2 权限最小化原则
sql复制-- 创建仅具有执行权限的专用用户
CREATE USER 'report_user'@'localhost' IDENTIFIED BY 'secure_password';
GRANT EXECUTE ON PROCEDURE db_name.GenerateSalesReport TO 'report_user'@'localhost';
10.3 敏感数据保护
sql复制CREATE PROCEDURE GetCustomerInfo(
IN cust_id INT,
IN requester_role VARCHAR(20)
)
BEGIN
IF requester_role = 'admin' THEN
SELECT * FROM customers WHERE id = cust_id;
ELSEIF requester_role = 'csr' THEN
SELECT id, name, email, phone FROM customers WHERE id = cust_id;
ELSE
SELECT id, name FROM customers WHERE id = cust_id;
END IF;
END
11. 版本控制与变更管理
11.1 存储过程版本化
sql复制CREATE PROCEDURE usp_GetCustomerOrders_v1_2(
IN cust_id INT,
IN include_cancelled BOOLEAN
)
BEGIN
IF include_cancelled THEN
SELECT * FROM orders
WHERE customer_id = cust_id;
ELSE
SELECT * FROM orders
WHERE customer_id = cust_id
AND status != 'cancelled';
END IF;
END
11.2 变更日志记录
sql复制CREATE TABLE procedure_changes (
id INT AUTO_INCREMENT PRIMARY KEY,
procedure_name VARCHAR(100) NOT NULL,
change_type ENUM('CREATE','ALTER','DROP') NOT NULL,
change_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
changed_by VARCHAR(50) NOT NULL,
sql_text TEXT NOT NULL
);
CREATE TRIGGER after_procedure_create
AFTER CREATE ON SCHEMA
FOR EACH ROW
BEGIN
IF NEW.type = 'PROCEDURE' THEN
INSERT INTO procedure_changes
(procedure_name, change_type, changed_by, sql_text)
VALUES (
NEW.name,
'CREATE',
CURRENT_USER(),
NEW.create_statement
);
END IF;
END;
12. 性能对比:存储过程 vs 应用代码
12.1 网络开销对比
存储过程将处理逻辑放在数据库端,减少了网络往返次数。例如批量插入操作:
应用代码方式(伪代码):
python复制for item in items:
execute("INSERT INTO table VALUES (...)")
存储过程方式:
sql复制CREATE PROCEDURE BatchInsert(IN items JSON)
BEGIN
-- 单次网络调用处理所有数据
END
12.2 执行计划重用
存储过程的预编译特性使得执行计划可以重用,而动态SQL每次都需要重新解析和优化。
12.3 事务处理效率
存储过程中的事务在数据库内部处理,比应用层事务更高效:
sql复制CREATE PROCEDURE TransferFunds(
IN from_acc INT,
IN to_acc INT,
IN amount DECIMAL(10,2)
)
BEGIN
START TRANSACTION;
-- 账户操作...
COMMIT;
END
相比应用代码中的事务:
java复制conn.setAutoCommit(false);
try {
// 执行多个SQL
conn.commit();
} catch (Exception e) {
conn.rollback();
}
13. 常见问题解决方案
13.1 调试技巧
- 使用SELECT输出中间结果:
sql复制CREATE PROCEDURE DebugExample()
BEGIN
DECLARE var1 INT DEFAULT 10;
SELECT 'Debug point 1', var1; -- 调试输出
-- 业务逻辑...
SET var1 = var1 * 2;
SELECT 'Debug point 2', var1; -- 调试输出
END
- 使用SIGNAL输出错误信息:
sql复制CREATE PROCEDURE ValidateInput(IN input INT)
BEGIN
IF input < 0 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Input must be positive';
END IF;
END
13.2 性能优化
- 避免在循环中查询:
sql复制-- 不好的做法
WHILE i < 100 DO
SELECT price INTO item_price FROM products WHERE id = i;
-- ...
END WHILE;
-- 好的做法
SELECT id, price INTO product_ids, product_prices FROM products;
-- 然后处理内存中的数据
- 合理使用临时表:
sql复制CREATE PROCEDURE ComplexReport()
BEGIN
CREATE TEMPORARY TABLE temp_data AS
SELECT ... FROM ... WHERE ...;
-- 基于临时表的复杂处理
SELECT ... FROM temp_data JOIN ...;
DROP TEMPORARY TABLE temp_data;
END
13.3 维护建议
-
统一命名规范:
- 前缀:usp_ (user stored procedure)
- 动词开头:Get, Update, Delete, Process
- 版本后缀:_v1, _v2
-
模块化设计:
sql复制CREATE PROCEDURE MainProcess()
BEGIN
CALL ValidateInputs();
CALL ProcessData();
CALL GenerateOutputs();
END
14. MySQL 8.0新特性应用
14.1 窗口函数支持
sql复制CREATE PROCEDURE RankProducts()
BEGIN
SELECT
id, name, price,
RANK() OVER (ORDER BY price DESC) AS price_rank,
DENSE_RANK() OVER (ORDER BY price DESC) AS dense_price_rank,
NTILE(4) OVER (ORDER BY price DESC) AS price_quartile
FROM products;
END
14.2 CTE (Common Table Expressions)
sql复制CREATE PROCEDURE GetOrgChart()
BEGIN
WITH RECURSIVE org_hierarchy AS (
-- 基础查询:顶级管理者
SELECT id, name, title, manager_id, 1 AS level
FROM employees
WHERE manager_id IS NULL
UNION ALL
-- 递归查询:下属员工
SELECT e.id, e.name, e.title, e.manager_id, h.level + 1
FROM employees e
JOIN org_hierarchy h ON e.manager_id = h.id
)
SELECT * FROM org_hierarchy ORDER BY level, name;
END
14.3 JSON增强支持
sql复制CREATE PROCEDURE ProcessJSONOrder(IN order_json JSON)
BEGIN
DECLARE customer_id INT;
DECLARE order_items JSON;
-- 提取JSON数据
SET customer_id = JSON_UNQUOTE(JSON_EXTRACT(order_json, '$.customer_id'));
SET order_items = JSON_EXTRACT(order_json, '$.items');
-- 处理订单
INSERT INTO orders (customer_id, order_date)
VALUES (customer_id, NOW());
SET @order_id = LAST_INSERT_ID();
-- 处理订单项
SET @i = 0;
SET @count = JSON_LENGTH(order_items);
WHILE @i < @count DO
SET @product_id = JSON_EXTRACT(order_items, CONCAT('$[', @i, '].product_id'));
SET @quantity = JSON_EXTRACT(order_items, CONCAT('$[', @i, '].quantity'));
INSERT INTO order_items (order_id, product_id, quantity)
VALUES (@order_id, @product_id, @quantity);
SET @i = @i + 1;
END WHILE;
END
15. 存储过程替代方案
15.1 自定义函数
当需要返回单个值时,函数更合适:
sql复制CREATE FUNCTION CalculateDiscount(
customer_level VARCHAR(20),
order_total DECIMAL(10,2)
)
RETURNS DECIMAL(3,2)
DETERMINISTIC
BEGIN
DECLARE discount DECIMAL(3,2) DEFAULT 0.00;
IF customer_level = 'gold' AND order_total > 100 THEN
SET discount = 0.15;
ELSEIF customer_level = 'silver' AND order_total > 50 THEN
SET discount = 0.10;
ELSEIF order_total > 200 THEN
SET discount = 0.05;
END IF;
RETURN discount;
END
15.2 触发器
自动化数据相关操作:
sql复制CREATE TRIGGER after_order_insert
AFTER INSERT ON orders
FOR EACH ROW
BEGIN
-- 更新客户最后订单日期
UPDATE customers
SET last_order_date = NEW.order_date
WHERE id = NEW.customer_id;
-- 记录订单事件
INSERT INTO order_events (order_id, event_type, event_time)
VALUES (NEW.id, 'created', NOW());
END
15.3 事件调度器
定期执行维护任务:
sql复制CREATE EVENT daily_maintenance
ON SCHEDULE EVERY 1 DAY
STARTS CURRENT_TIMESTAMP
DO
BEGIN
-- 清理过期会话
DELETE FROM user_sessions
WHERE last_activity < NOW() - INTERVAL 30 DAY;
-- 备份关键数据
INSERT INTO data_backups (table_name, backup_date, row_count)
SELECT 'products', NOW(), COUNT(*) FROM products;
-- 更新统计信息
CALL UpdateProductStatistics();
END
16. 复杂业务逻辑实现
16.1 递归查询处理层级数据
sql复制CREATE PROCEDURE GetEmployeeHierarchy(IN top_manager_id INT)
BEGIN
-- 使用递归CTE查询组织架构
WITH RECURSIVE emp_hierarchy AS (
-- 基础查询:顶级管理者
SELECT id, name, title, 0 AS level, CAST(name AS CHAR(200)) AS path
FROM employees
WHERE id = top_manager_id
UNION ALL
-- 递归查询:下属员工
SELECT e.id, e.name, e.title, h.level + 1,
CONCAT(h.path, ' > ', e.name) AS path
FROM employees e
JOIN emp_hierarchy h ON e.manager_id = h.id
)
SELECT id, name, title, level, path
FROM emp_hierarchy
ORDER BY path;
END
16.2 复杂报表生成
sql复制CREATE PROCEDURE GenerateFinancialReport(
IN start_date DATE,
IN end_date DATE,
IN group_by VARCHAR(20)
)
BEGIN
-- 创建临时表存储汇总数据
CREATE TEMPORARY TABLE IF NOT EXISTS temp_report (
period VARCHAR(20),
revenue DECIMAL(12,2),
cost DECIMAL(12,2),
profit DECIMAL(12,2),
margin DECIMAL(5,2)
);
-- 根据分组参数填充数据
CASE group_by
WHEN 'month' THEN
INSERT INTO temp_report
SELECT
DATE_FORMAT(order_date, '%Y-%m') AS period,
SUM(amount) AS revenue,
SUM(cost) AS cost,
SUM(amount - cost) AS profit,
ROUND(SUM(amount - cost) / SUM(amount) * 100, 2) AS margin
FROM orders o
JOIN (
SELECT order_id, SUM(unit_cost * quantity) AS cost
FROM order_items
GROUP BY order_id
) oi ON o.id = oi.order_id
WHERE order_date BETWEEN start_date AND end_date
GROUP BY DATE_FORMAT(order_date, '%Y-%m');
WHEN 'product' THEN
INSERT INTO temp_report
SELECT
p.name AS period,
SUM(oi.price * oi.quantity) AS revenue,
SUM(oi.unit_cost * oi.quantity) AS cost,
SUM(oi.price * oi.quantity - oi.unit_cost * oi.quantity) AS profit,
ROUND(
SUM(oi.price * oi.quantity - oi.unit_cost * oi.quantity) /
SUM(oi.price * oi.quantity) * 100,
2
) AS margin
FROM order_items oi
JOIN orders o ON oi.order_id = o.id
JOIN products p ON oi
