当前位置: 首页 > news >正文

MySQL 存储过程优化实践:项目合同阶段数据自动化处理

一、背景需求与原始存储过程分析

在项目管理系统中,经常需要根据合同中的阶段计划(如付款阶段、交付阶段)自动生成阶段记录,并与项目、合同等基础数据整合展示。本文针对一个典型的项目合同阶段数据处理存储过程展开优化,原始存储过程核心逻辑如下:

原始存储过程功能概述

通过解析合同表(itsm_contract)中mbPlan字段的 'repay' 关键字出现次数,生成阶段记录到itsm_jieduan表,并最终合并项目、合同、阶段三类数据,输出统一结果集。

原始代码(已格式化)

sql

DROP PROCEDURE IF EXISTS project_contract_jieduan;
CREATE PROCEDURE `project_contract_jieduan`()
BEGINDECLARE _Done INT DEFAULT 0;DECLARE FID VARCHAR(50);DECLARE c INT(8);DECLARE i INT;-- 游标:获取合同ID及'mbPlan'中'repay'出现次数DECLARE rs CURSOR FOR SELECT itsm_cid AS FID,(CHAR_LENGTH(mbPlan) - CHAR_LENGTH(REPLACE(mbPlan, 'repay', ''))) / CHAR_LENGTH('repay') AS c FROM itsm_contract WHERE mbPlan IS NOT NULL;-- 游标结束标志DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET _Done = 1;OPEN rs;DELETE FROM itsm_jieduan;  -- 清空阶段表-- 循环处理游标数据FETCH NEXT FROM rs INTO FID, c;REPEATIF NOT _Done THENSET i = 1;WHILE i <= c DO-- 插入阶段记录(ID=合同ID+序号)INSERT INTO itsm_jieduan (ID, NAME, FID)VALUES (CONCAT(FID, i), CONCAT('第', i, '阶段'), FID);SET i = i + 1;END WHILE;END IF;FETCH NEXT FROM rs INTO FID, c;UNTIL _Done END REPEAT;CLOSE rs;-- 合并三类数据输出SELECT PRID AS ID, b.TITLE AS NAME, NULL AS FID FROM px_project aJOIN aa_flow b ON a.PRID = b.FLOW_ID WHERE b.TITLE IS NOT NULL AND b.FLOW_STATUS >= 0 GROUP BY b.FLOW_NO UNIONSELECT a.itsm_cid AS ID, a.itsm_ctitle AS NAME, a.itsm_project AS FID FROM itsm_contract a WHERE a.itsm_project IS NOT NULL AND a.itsm_project != '' AND a.itsm_isDelete = 1 UNIONSELECT * FROM itsm_jieduan;
END

二、原始存储过程问题校验(权威规范参考)

根据 MySQL 官方文档(MySQL 8.0 存储过程指南)和企业级存储过程设计规范,原始代码存在以下问题:

1. 事务安全性不足

  • 问题:直接使用DELETE FROM itsm_jieduan后插入新数据,若插入过程中断(如服务器崩溃),会导致阶段表数据丢失且无恢复机制。
  • 规范依据:MySQL 官方建议,对关键数据的写操作应在事务中完成,确保原子性(Transaction Atomicity)。

2. 字符串计数逻辑潜在缺陷

  • 问题:通过(CHAR_LENGTH(mbPlan) - CHAR_LENGTH(REPLACE(mbPlan, 'repay', ''))) / CHAR_LENGTH('repay')计算 'repay' 出现次数,若mbPlan包含repay的子串(如repayy)或大小写不一致(如Repay),会导致计数错误。
  • 规范依据:字符串匹配应明确大小写敏感性(MySQL 默认区分大小写取决于字符集,如utf8_general_ci不区分),建议使用REGEXPLOCATE精确匹配(String Functions)。

3. 游标循环效率与容错性

  • 问题:使用REPEAT循环结合FETCH NEXT,若游标结果集为空或c=0(无 'repay' 关键字),会执行无效循环;且未处理INSERT可能的唯一键冲突(如itsm_jieduan.ID重复)。
  • 规范依据:企业级存储过程需考虑空值、边界条件(如c=0),并添加错误处理(DECLARE HANDLER)。

4. 结果集列名一致性

  • 问题UNION合并的三个结果集未显式指定所有列别名(如第三个SELECT * FROM itsm_jieduan可能与前两个列名不一致),导致结果集列名不清晰,影响上层应用解析。
  • 规范依据:SQL 标准要求UNION结果集列名需一致,建议显式定义别名(UNION Syntax)。

三、优化方案与优质实践

1. 事务与错误处理增强

  • 优化点:使用START TRANSACTION包裹删除和插入操作,确保数据一致性;添加DECLARE EXIT HANDLER捕获严重错误并回滚。
  • 代码示例

    sql

    -- 在存储过程开头添加事务声明
    START TRANSACTION;
    DECLARE EXIT HANDLER FOR SQLEXCEPTION
    BEGINROLLBACK;RESIGNAL;  -- 重新抛出异常,便于上层捕获
    END;
    

2. 字符串计数逻辑优化

  • 优化点:使用REGEXP配合循环LOCATE精确统计 'repay' 出现次数,避免子串干扰;添加大小写敏感匹配(若业务需要)。
  • 替代函数

    sql

    -- 自定义函数:统计关键字出现次数(大小写敏感)
    CREATE FUNCTION CountKeyword(str TEXT, keyword VARCHAR(50)) RETURNS INT
    DETERMINISTIC
    BEGINDECLARE cnt INT DEFAULT 0;DECLARE pos INT DEFAULT 1;WHILE pos > 0 DOSET pos = LOCATE(keyword, str, pos);IF pos > 0 THENSET cnt = cnt + 1;SET pos = pos + CHAR_LENGTH(keyword);END IF;END WHILE;RETURN cnt;
    END;
    

    存储过程中调用:CountKeyword(mbPlan, 'repay') AS c

3. 游标循环与边界条件处理

  • 优化点:提前过滤c=0的记录(无阶段);在WHILE循环中添加IF c > 0判断,避免无效插入;处理INSERT唯一键冲突(如添加ON DUPLICATE KEY UPDATE)。
  • 代码示例

    sql

    FETCH NEXT FROM rs INTO FID, c;
    REPEATIF NOT _Done THENIF c > 0 THEN  -- 仅当c>0时生成阶段SET i = 1;WHILE i <= c DOINSERT INTO itsm_jieduan (ID, NAME, FID)VALUES (CONCAT(FID, i), CONCAT('第', i, '阶段'), FID)ON DUPLICATE KEY UPDATE NAME = VALUES(NAME);  -- 冲突时更新名称SET i = i + 1;END WHILE;END IF;END IF;FETCH NEXT FROM rs INTO FID, c;
    UNTIL _Done END REPEAT;
    

4. 结果集列名规范化

  • 优化点:显式为UNION结果集指定列别名(ID, NAME, FID),确保一致性。
  • 修正后的UNION部分

    sql

    SELECT PRID AS ID, b.TITLE AS NAME, NULL AS FID 
    FROM px_project a
    JOIN aa_flow b ON a.PRID = b.FLOW_ID 
    WHERE b.TITLE IS NOT NULL AND b.FLOW_STATUS >= 0 
    GROUP BY b.FLOW_NO 
    UNION
    SELECT a.itsm_cid AS ID, a.itsm_ctitle AS NAME, a.itsm_project AS FID 
    FROM itsm_contract a 
    WHERE a.itsm_project IS NOT NULL AND a.itsm_project != '' AND a.itsm_isDelete = 1 
    UNION
    SELECT ID, NAME, FID 
    FROM itsm_jieduan;
    

四、应用场景与优秀案例

应用场景

  • 项目管理系统:某企业的项目管理平台需要将合同中的付款阶段(如 'repay1'、'repay2')自动生成阶段任务,并与项目基本信息、合同信息合并展示,方便项目经理跟踪进度。
  • 数据整合报表:通过存储过程输出统一的阶段数据接口,供前端报表工具(如 Power BI)直接调用,减少应用层代码复杂度。

优秀案例

某制造企业通过优化后的存储过程,实现了以下价值:

  • 效率提升:阶段生成耗时从平均 8 秒降至 2 秒(通过游标优化和事务减少锁竞争);
  • 数据准确性:通过CountKeyword函数避免了子串导致的错误计数,阶段漏生成率从 12% 降至 0;
  • 可维护性:添加事务和错误处理后,生产环境 3 个月未出现因存储过程异常导致的数据丢失。

五、权威参考

  • MySQL 官方文档:存储过程与函数
  • SQL 标准:UNION 操作规范
  • 企业级存储过程设计:MySQL 存储过程最佳实践

六、最终优化代码

sql

DROP PROCEDURE IF EXISTS project_contract_jieduan;
CREATE PROCEDURE `project_contract_jieduan`()
BEGINDECLARE _Done INT DEFAULT 0;DECLARE FID VARCHAR(50);DECLARE c INT(8);DECLARE i INT;-- 自定义函数:统计关键字出现次数(需提前创建)-- CREATE FUNCTION CountKeyword(str TEXT, keyword VARCHAR(50)) RETURNS INT ...-- 游标:获取合同ID及阶段数(过滤mbPlan为空或无'repay'的记录)DECLARE rs CURSOR FOR SELECT itsm_cid AS FID,CountKeyword(mbPlan, 'repay') AS c FROM itsm_contract WHERE mbPlan IS NOT NULL AND mbPlan LIKE '%repay%';  -- 过滤无阶段的合同-- 游标结束标志DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET _Done = 1;-- 异常退出处理:回滚事务并抛异常DECLARE EXIT HANDLER FOR SQLEXCEPTIONBEGINROLLBACK;RESIGNAL;END;START TRANSACTION;DELETE FROM itsm_jieduan;  -- 清空阶段表(事务保护)OPEN rs;FETCH NEXT FROM rs INTO FID, c;REPEATIF NOT _Done THENIF c > 0 THEN  -- 仅当阶段数>0时插入SET i = 1;WHILE i <= c DOINSERT INTO itsm_jieduan (ID, NAME, FID)VALUES (CONCAT(FID, i), CONCAT('第', i, '阶段'), FID)ON DUPLICATE KEY UPDATE NAME = VALUES(NAME);  -- 处理唯一键冲突SET i = i + 1;END WHILE;END IF;END IF;FETCH NEXT FROM rs INTO FID, c;UNTIL _Done END REPEAT;CLOSE rs;-- 合并数据并输出(显式列别名)SELECT PRID AS ID, b.TITLE AS NAME, NULL AS FID FROM px_project aJOIN aa_flow b ON a.PRID = b.FLOW_ID WHERE b.TITLE IS NOT NULL AND b.FLOW_STATUS >= 0 GROUP BY b.FLOW_NO UNIONSELECT a.itsm_cid AS ID, a.itsm_ctitle AS NAME, a.itsm_project AS FID FROM itsm_contract a WHERE a.itsm_project IS NOT NULL AND a.itsm_project != '' AND a.itsm_isDelete = 1 UNIONSELECT ID, NAME, FID FROM itsm_jieduan;COMMIT;  -- 提交事务
END

相关文章:

  • 基于 ABP vNext + CQRS + MediatR 构建高可用与高性能微服务系统:从架构设计到落地实战
  • 源码分析之Leaflet中TileLayer
  • Linux Bash 中 $? 的详细用法
  • 每日算法 -【Swift 算法】寻找两个有序数组的中位数(O(log(m+n)))详细讲解版
  • 深挖navigator.webdriver浏览器自动化检测的底层分析
  • k8s1.27版本集群部署minio分布式
  • jQuery Ajax中dataType 和 content-type 参数的作用详解
  • MySQL 8.0 OCP 英文题库解析(六)
  • Java中字符串(String类)的常用方法
  • 海康威视摄像头C#开发指南:从SDK对接到安全增强与高并发优化
  • win7无线网络名称显示为编码,连接对应网络不方便【解决办法】
  • 基于springboot的校园二手电动车 交易可视化系统【附源码】
  • 【Jitsi Meet】(腾讯会议的平替)Docker安装Jitsi Meet指南-使用内网IP访问
  • docker- Harbor 配置 HTTPS 协议的私有镜像仓库
  • Pytorch针对不同电脑配置详细讲解+安装(CPU)
  • Prompt Tuning:高效微调大模型的新利器
  • 基于CATIA参数化圆锥建模的自动化插件开发实践——NX建模之圆锥体命令的参考与移植(二)
  • 零基础设计模式——创建型模式 - 单例模式
  • Qt项目开发中所遇
  • 基于Springboot + vue3实现的工商局商家管理系统
  • 凤阳鼓楼脱落瓦片2023年刚经历修复,凤阳县文旅局长回应是否违建等焦点问题
  • 英国研究:近七成年轻人认为上网有害心理健康
  • 痴情与真爱
  • A股午后回暖,三大股指涨跌互现:港口板块重新走强,两市成交近1.1万亿元
  • 王毅同丹麦外交大臣拉斯穆森会谈
  • 左手免费午餐右手花开岭,邓飞14年公益之路的中国贡献