INOUT参数是会话变量的引用,非返回值;调用时必须传已初始化的用户变量,过程内SET操作实时写回,支持累加等高效计算,但不可与OUT混用同一变量。

INOUT参数不是“返回值”,而是会话变量的引用
INOUT参数在MySQL里不走“返回”路径,它直接把传入的用户变量地址带进过程体,所有SET x = ...操作都实时写回原变量。这意味着你不需要等CALL结束再查——只要过程里改了,外部立刻可见。
常见错误是把它当OUT用:比如写CALL p(@x)后还傻等,其实@x在过程第2行就被覆盖了。调试时可以加SELECT @x;在过程中间验证,这在OUT里完全无效。
-
INOUT必须传已声明的用户变量,不能传字面量(如CALL p(123)会报错) - 过程内对
INOUT变量赋值前,建议先IF x IS NULL THEN SET x = 0; END IF;,避免NULL参与计算导致整条表达式变NULL - 如果过程里有多个
SET x = ...,最后一次赋值才是最终结果;中间值不会丢,但没意义
用INOUT做累加器比临时表或游标快得多
当你需要在循环中累计数值(比如统计满足条件的记录数、求和、拼接字符串),用INOUT变量比建临时表或开游标轻量十倍以上。MySQL不用为它分配额外存储,也不触发磁盘I/O。
示例场景:遍历一个ID列表并累加对应金额
DELIMITER $$
CREATE PROCEDURE sum_amounts(IN id_list TEXT, OUT total DECIMAL(10,2))
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE cur_id INT;
DECLARE cur_amt DECIMAL(10,2);
DECLARE ids CURSOR FOR SELECT id FROM JSON_TABLE(id_list, '$[*]' COLUMNS (id INT PATH '$')) t;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
SET total = 0;
OPEN ids;
read_loop: LOOP
FETCH ids INTO cur_id;
IF done THEN
LEAVE read_loop;
END IF;
SELECT amount INTO cur_amt FROM orders WHERE order_id = cur_id;
SET total = total + IFNULL(cur_amt, 0);
END LOOP;
CLOSE ids;
END$$
DELIMITER ;
上面这段用了游标+OUT,但如果改成INOUT total DECIMAL(10,2),就能省掉SET total = 0初始化那句——调用方自己设好初始值就行,过程只负责叠加。
客户端调用INOUT时必须显式初始化用户变量
哪怕你只想让它“传进去再传出来”,也得先SET @var := 0或SET @var := DEFAULT。MySQL 8.0 strict模式下,不初始化就CALL会直接报Unknown column '@var' in 'field list'。
- 初始化值决定计算起点:想累加就设
0,想拼字符串就设'',想计数就设0 - 别在过程里再
SET @var = ...——这是在改会话变量,不是给INOUT赋值;正确写法永远是SET x = ...(注意没有@) - 一次
CALL可传多个INOUT,比如CALL calc_stats(@sum, @count, @max),它们互不干扰
INOUT和OUT混用时最容易踩的坑是变量覆盖
如果你写CREATE PROCEDURE p(INOUT a INT, OUT b INT),然后调用CALL p(@x, @x)(把同一个变量既当INOUT又当OUT),结果不可预测:INOUT实时写,OUT延迟写,最后@x到底是哪个值取决于执行顺序,且不同MySQL版本行为可能不一致。
安全做法永远是:每个INOUT和OUT参数绑定独立变量。
真正容易被忽略的是——INOUT变量一旦被过程修改,就再也无法回滚到调用前的状态。如果你需要原始值做对比(比如“变化了多少”),必须在CALL前用另一个变量存一份副本。


















