1311 字
7 分钟
从入门到精通MySQL存储过程:复杂场景实战指南
从入门到精通MySQL存储过程:复杂场景实战指南
一、存储过程的核心价值
存储过程是预编译的SQL语句集合,具备四大核心优势:
- 网络传输效率提升(减少70%+的查询请求)
- 业务逻辑封装(实现服务层与数据层的解耦)
- 原子操作保障(通过事务管理实现)
- 权限集中控制(精确到存储过程级别的权限分配)
二、快速入门:基础语法结构
2.1 创建首个存储过程
DELIMITER $$
CREATE PROCEDURE GetUserLevel(IN userId INT, OUT userLevel VARCHAR(20))
BEGIN
SELECT
CASE
WHEN purchase_total > 10000 THEN '钻石会员'
WHEN purchase_total > 5000 THEN '黄金会员'
ELSE '普通会员'
END INTO userLevel
FROM user_stats
WHERE user_id = userId;
END$$
DELIMITER ;
2.2 执行与结果获取
CALL GetUserLevel(1024, @level);
SELECT @level AS user_level;
三、进阶开发:复杂语法要素
3.1 多类型参数处理
CREATE PROCEDURE ProcessOrder(
IN orderId INT,
INOUT retryCount INT,
OUT errorCode INT
)
3.2 游标与异常处理
DECLARE orderCursor CURSOR FOR
SELECT product_id, quantity, unit_price
FROM order_items
WHERE order_id = orderId;
DECLARE CONTINUE HANDLER FOR SQLSTATE '23000'
BEGIN
SET errorCode = 1001;
ROLLBACK;
END;
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
GET DIAGNOSTICS CONDITION 1
@sqlstate = RETURNED_SQLSTATE,
@errno = MYSQL_ERRNO,
@text = MESSAGE_TEXT;
INSERT INTO error_logs VALUES (NOW(), @errno, @text);
ROLLBACK;
END;
3.3 多层条件判断
IF inventory_qty < required_qty THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '库存不足';
ELSEIF inventory_qty - required_qty < min_stock THEN
UPDATE inventory SET status = '需补货' WHERE product_id = currentProduct;
END IF;
四、精通实战:电商订单拆解系统
4.1 场景需求
处理包含以下复杂逻辑的订单:
- 多商品库存验证
- 自动拆单(区分现货与预售)
- 会员积分实时计算
- 分布式事务补偿机制
4.2 完整存储过程实现
DELIMITER $$
CREATE PROCEDURE ProcessComplexOrder(
IN mainOrderId BIGINT,
IN isSplitAllowed BOOLEAN,
OUT resultCode INT,
OUT errorMsg TEXT
)
proc_label: BEGIN
DECLARE vUserId INT;
DECLARE vTotalAmount DECIMAL(12,2);
DECLARE vCurrentProduct INT;
DECLARE vRequiredQty INT;
DECLARE vWarehouseId INT;
DECLARE done INT DEFAULT FALSE;
-- 声明嵌套游标
DECLARE mainCursor CURSOR FOR
SELECT product_id, quantity, warehouse_id
FROM order_details
WHERE order_id = mainOrderId;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
-- 事务开始
START TRANSACTION;
-- 验证主订单有效性
SELECT user_id, total_amount INTO vUserId, vTotalAmount
FROM orders
WHERE order_id = mainOrderId
FOR UPDATE;
IF vUserId IS NULL THEN
SET resultCode = 404;
SET errorMsg = '主订单不存在';
ROLLBACK;
LEAVE proc_label;
END IF;
-- 开启游标处理
OPEN mainCursor;
item_loop: LOOP
FETCH mainCursor INTO vCurrentProduct, vRequiredQty, vWarehouseId;
IF done THEN
LEAVE item_loop;
END IF;
-- 库存验证子过程
CALL CheckInventory(
vCurrentProduct,
vRequiredQty,
vWarehouseId,
@stockStatus,
@availableQty
);
-- 库存不足处理逻辑
IF @stockStatus = 'INSUFFICIENT' THEN
IF isSplitAllowed THEN
-- 创建拆分订单
INSERT INTO split_orders
VALUES (NULL, mainOrderId, vCurrentProduct, vRequiredQty);
UPDATE order_details
SET quantity = @availableQty
WHERE product_id = vCurrentProduct
AND order_id = mainOrderId;
ELSE
SET resultCode = 500;
SET errorMsg = CONCAT('产品 ', vCurrentProduct, ' 库存不足');
ROLLBACK;
LEAVE proc_label;
END IF;
END IF;
-- 实际库存扣减
UPDATE product_stock
SET current_stock = current_stock - vRequiredQty,
locked_stock = locked_stock + vRequiredQty
WHERE product_id = vCurrentProduct
AND warehouse_id = vWarehouseId;
END LOOP;
CLOSE mainCursor;
-- 积分计算(含阶梯规则)
SET @points = CASE
WHEN vTotalAmount > 1000 THEN FLOOR(vTotalAmount * 0.15)
WHEN vTotalAmount > 500 THEN FLOOR(vTotalAmount * 0.1)
ELSE FLOOR(vTotalAmount * 0.05)
END;
-- 更新用户积分(原子操作)
UPDATE user_points
SET total_points = total_points + @points,
available_points = available_points + @points
WHERE user_id = vUserId;
-- 记录积分日志
INSERT INTO points_log
VALUES (NULL, vUserId, mainOrderId, @points, NOW());
-- 提交事务
COMMIT;
SET resultCode = 200;
SET errorMsg = '订单处理成功';
END$$
DELIMITER ;
4.3 配套子过程:库存检查
CREATE PROCEDURE CheckInventory(
IN productId INT,
IN requiredQty INT,
IN warehouseId INT,
OUT stockStatus VARCHAR(20),
OUT availableQty INT
)
BEGIN
DECLARE currentStock INT;
DECLARE safetyStock INT DEFAULT 10;
SELECT (current_stock - locked_stock)
INTO currentStock
FROM product_stock
WHERE product_id = productId
AND warehouse_id = warehouseId;
IF currentStock - requiredQty >= safetyStock THEN
SET stockStatus = 'SUFFICIENT';
SET availableQty = requiredQty;
ELSEIF currentStock > safetyStock THEN
SET stockStatus = 'PARTIAL';
SET availableQty = currentStock - safetyStock;
ELSE
SET stockStatus = 'INSUFFICIENT';
SET availableQty = 0;
END IF;
END;
五、高级优化策略
5.1 性能调优技巧
- 游标优化:改用JOIN查询替代逐行处理
UPDATE order_details od
JOIN product_stock ps ON od.product_id = ps.product_id
SET od.status = '已分配'
WHERE od.order_id = mainOrderId
AND ps.current_stock - ps.locked_stock >= od.quantity;
- 执行计划分析:使用EXPLAIN解析复杂查询
EXPLAIN FORMAT=JSON
SELECT ... FROM orders
JOIN order_details ...;
- 缓存优化:合理使用查询缓存
SELECT SQL_CACHE * FROM product_info
WHERE product_id = 123;
5.2 安全防护方案
- SQL注入防御
CREATE PROCEDURE SafeQuery(IN filterParam VARCHAR(50))
BEGIN
SET @query = CONCAT('SELECT * FROM products WHERE name = ', QUOTE(filterParam));
PREPARE stmt FROM @query;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END;
- 权限隔离配置
GRANT EXECUTE ON PROCEDURE ProcessComplexOrder TO 'order_processor'@'%';
REVOKE DELETE ON orders FROM 'order_processor'@'%';
六、调试与监控
6.1 调试技巧
-- 开启过程跟踪
SET global log_bin_trust_function_creators = 1;
SET global general_log = 1;
-- 使用SELECT调试变量
SELECT @currentProduct AS debug_product, @availableQty AS debug_stock;
6.2 性能监控
-- 查看存储过程执行统计
SELECT * FROM sys.routine_stats
WHERE routine_name = 'ProcessComplexOrder';
-- 分析过程执行耗时
SHOW PROFILE CPU, BLOCK IO FOR QUERY 123;
七、最佳实践总结
- 事务粒度控制:单个事务不超过50条DML语句
- 错误处理层级:至少包含三级错误处理(SQL异常、业务异常、警告)
- 游标使用原则:万级以下数据量使用,大数据量建议分页处理
- 版本控制方案:使用
CREATE OR REPLACE
进行版本迭代 - 性能基准测试:定期进行压力测试,推荐使用sysbench工具
通过本指南的进阶案例,开发者可以处理包含分布式事务、库存预占、订单拆解、积分计算等复杂场景。实际应用中建议结合具体的业务需求,对事务隔离级别、错误处理机制进行针对性优化。
从入门到精通MySQL存储过程:复杂场景实战指南
https://turnip1202.github.io/my-blog-astro/posts/study/mysql-study1/