MySQL存储过程中的SQL默认为静态原生语句,需写在BEGIN...END内且以;结尾;变量赋值须用SELECT...INTO或SET=(SELECT...);动态SQL需PREPARE+EXECUTE并校验标识符;事务需显式控制。

存储过程中直接写SQL语句是默认行为
MySQL 存储过程里写的 SELECT、INSERT、UPDATE、DELETE 都是原生 SQL,不需要额外包装或转义。只要语法合法、上下文有权限,就能执行。常见误区是以为要像动态 SQL 那样拼字符串——其实静态 SQL 就是直接写。
注意点:
- 所有 SQL 语句末尾必须加
;,否则会报语法错误(尤其在DELIMITER切换后) - 不能在存储过程体外写 SQL;必须包裹在
BEGIN ... END块中 - 表名、列名不支持变量替换(除非走
PREPARE+EXECUTE动态方式)
用 SET 和 SELECT INTO 给变量赋值
想把查询结果存进存储过程变量,不能用普通 SELECT 输出结果集,得用 SELECT ... INTO 或 SET ... = (SELECT ...)。
比如:
DECLARE v_count INT DEFAULT 0; SELECT COUNT(*) INTO v_count FROM users WHERE status = 'active';
或者:
SET v_count = (SELECT COUNT(*) FROM users WHERE status = 'active');
区别:
-
SELECT ... INTO要求查询**恰好返回一行一列**,多行会报错ERROR 1172 (42000): Result consisted of more than one row -
SET ... = (SELECT ...)同样只接受单值,但空结果会赋NULL,而SELECT ... INTO空结果赋默认值(如INT类型为0) - 如果要处理多行结果,得用游标(
DECLARE cursor_name CURSOR FOR ...),不是简单 SQL 混合能解决的
动态 SQL 必须用 PREPARE + EXECUTE
当表名、列名、WHERE 条件需要运行时决定,就得拼接字符串再执行。MySQL 不允许直接把变量当标识符用,比如 SELECT * FROM @table_name 是非法的。
正确写法分三步:
- 用
CONCAT()拼出完整 SQL 字符串,存入用户变量(如@sql) - 用
PREPARE stmt FROM @sql编译 - 用
EXECUTE stmt执行,必要时用USING传参防止注入
示例:
SET @table = 'orders';
SET @status = 'shipped';
SET @sql = CONCAT('SELECT COUNT(*) FROM ', @table, ' WHERE status = ?');
PREPARE stmt FROM @sql;
EXECUTE stmt USING @status;
DEALLOCATE PREPARE stmt;关键提醒:
-
PREPARE只在当前会话有效,不能跨连接复用 - 拼接表名/列名时务必白名单校验,避免 SQL 注入(
USING只防参数,不防标识符) - 执行完记得
DEALLOCATE PREPARE,否则可能耗尽会话资源
事务控制需显式声明 BEGIN / COMMIT / ROLLBACK
存储过程默认不自动开启事务。哪怕里面写了多条 DML,也不具备原子性——除非你手动加 START TRANSACTION 或 BEGIN(二者等价)。
典型结构:
START TRANSACTION; INSERT INTO log_table VALUES (...); UPDATE account SET balance = balance - 100 WHERE id = 123; IF ROW_COUNT() = 0 THEN ROLLBACK; ELSE COMMIT; END IF;
注意:
-
COMMIT和ROLLBACK会影响整个事务,不只是存储过程内操作 - 如果调用方已开启事务,存储过程里的
COMMIT会提前结束它(可能破坏上层逻辑) - 建议存储过程只做 DML,由调用方统一控事务;若必须自管,记得文档注明“本过程含事务控制”
实际用的时候,最常踩的坑是混淆静态 SQL 和动态 SQL 的能力边界:以为变量能直接当表名用,或漏写 INTO 导致结果集意外返回、触发客户端报错。动态拼接那块尤其容易在线上被注入,别图省事跳过校验。










