MySQL触发器无法执行绑定动态查询的游标异常问题
问题根因
触发器失效是多类语法错误、数据库方言不兼容问题叠加导致的,核心问题如下:
- 数据库方言混用:代码大量照搬Oracle存储过程语法,MySQL原生不支持这些写法:
- 不支持
open 游标 for 动态SQL字符串的动态游标语法 - 默认不支持
||做字符串拼接(默认状态下||是逻辑或运算符) - 不存在
dbms_output.put_line系统存储过程,直接调用会直接抛出运行时错误
- 不支持
- 缺失对象声明:代码中直接打开
search_cursor游标,但全程没有声明该游标,触发对象不存在错误 - DECLARE顺序错误:MySQL要求BEGIN块内必须先声明所有局部变量、再声明游标、最后声明异常处理程序,原代码变量、游标声明顺序混乱,还嵌套了多余的BEGIN块,极易触发语法错误
- 缺少异常容错:如果
work_request_detail/work_request_sap_details表中没有匹配work_request_id的行,FETCH游标时会抛出NOT FOUND异常,直接终止触发器执行,没有做异常捕获 - 转义逻辑错误:单引号转义规则套用了Oracle的写法,拼出来的动态SQL存在语法错误,就算能执行也会报SQL语法异常
修复方案
MySQL中不需要用动态游标实现count统计,直接用预处理语句(PREPARE/EXECUTE)执行动态SQL即可,同时修正所有语法错误、补全容错逻辑,修复后的完整代码如下:
DELIMITER $$ CREATE DEFINER = CURRENT_USER TRIGGER `WRT_BEFORE_UPDATE` BEFORE UPDATE ON `WRT` FOR EACH ROW BEGIN -- 局部变量声明 必须放在块最开头 DECLARE v_work_request_type VARCHAR(100); DECLARE v_task_title VARCHAR(200); DECLARE v_mandatory_critical_task VARCHAR(100); DECLARE v_trade VARCHAR(200); DECLARE v_count INT DEFAULT 0; DECLARE not_found_flag INT DEFAULT 0; -- 游标声明 放在变量声明之后 DECLARE CUR1 CURSOR FOR SELECT work_request_type, REPLACE(task_title,'''','$') AS task_title, mandatory_critical_task FROM work_request_detail WHERE work_request_id = NEW.work_request_id; DECLARE CUR2 CURSOR FOR SELECT trade FROM work_request_sap_details WHERE work_request_id = NEW.work_request_id; -- 异常处理声明 放在所有声明最后 DECLARE CONTINUE HANDLER FOR NOT FOUND SET not_found_flag = 1; -- 初始化变量避免脏数据 SET v_work_request_type = NULL, v_task_title = NULL, v_mandatory_critical_task = NULL, v_trade = NULL; -- 读取关联表1数据 OPEN CUR1; FETCH CUR1 INTO v_work_request_type, v_task_title, v_mandatory_critical_task; CLOSE CUR1; -- 重置标记 读取关联表2数据 SET not_found_flag = 0; OPEN CUR2; FETCH CUR2 INTO v_trade; CLOSE CUR2; -- 用CONCAT拼接动态SQL 规避||运算符兼容问题 SET @qry_str = CONCAT('SELECT COUNT(*) INTO @cnt FROM dws_selection_settings WHERE WRT_type = ''', v_work_request_type, ''''); IF v_task_title IS NOT NULL AND LENGTH(TRIM(v_task_title)) > 0 THEN SET @qry_str = CONCAT(@qry_str, ' AND REPLACE(WRT_title, '''''', ''$'') = ''', v_task_title, ''''); ELSE SET @qry_str = CONCAT(@qry_str, ' AND WRT_title IS NULL'); END IF; IF v_mandatory_critical_task IS NOT NULL AND LENGTH(TRIM(v_mandatory_critical_task)) > 0 THEN SET @qry_str = CONCAT(@qry_str, ' AND mandatory_critical = ''', v_mandatory_critical_task, ''''); ELSE SET @qry_str = CONCAT(@qry_str, ' AND mandatory_critical IS NULL'); END IF; IF NEW.notification_id IS NOT NULL AND LENGTH(TRIM(NEW.notification_id)) > 0 THEN SET @qry_str = CONCAT(@qry_str, ' AND sap_reference_number = ''', NEW.notification_id, ''''); ELSE SET @qry_str = CONCAT(@qry_str, ' AND sap_reference_number IS NULL'); END IF; IF v_trade IS NOT NULL AND LENGTH(TRIM(v_trade)) > 0 THEN SET @qry_str = CONCAT(@qry_str, ' AND trade = ''', v_trade, ''''); ELSE SET @qry_str = CONCAT(@qry_str, ' AND trade IS NULL'); END IF; -- 执行动态SQL 无需使用动态游标 PREPARE stmt FROM @qry_str; EXECUTE stmt; DEALLOCATE PREPARE stmt; SET v_count = @cnt; -- 匹配到记录则直接修改NEW对象字段 IF v_count > 0 THEN SET NEW.auto_select_flag = 'Y'; END IF; END $$ DELIMITER ;
注意事项
- 请提前确认
work_request_id在work_request_detail、work_request_sap_details表中是唯一键,否则当前逻辑只会取匹配到的第一行数据,和预期逻辑不符 - 上述代码适用于MySQL 5.7及以上版本,低版本MySQL不支持在触发器中使用预处理语句
- 如果业务字段值中包含单引号,建议在拼接前对值做单引号转义(将值内的单引号替换为两个单引号),避免动态SQL语法错误或注入风险
- BEFORE UPDATE触发器中直接修改
NEW.字段名即可生效,不要写针对同一张WRT表的UPDATE语句,否则会触发触发器递归报错
内容的提问来源于stack exchange,提问作者Misha
相关产品推荐
相关产品推荐

