mysql会话级用户变量(如@var)在连接池复用时不自动清空,导致后续查询结果异常;应显式重置而非依赖reset session,并优先采用set @var = null等轻量方式控制作用域。

MySQL连接池中SET @var残留导致后续查询结果异常
连接重用时,会话级用户变量(如@last_id、@rownum)不会自动清空,下一个请求可能读到上一个请求遗留的值。这不是bug,是MySQL设计如此——会话变量生命周期=连接生命周期。
- 典型现象:
SELECT @counter := @counter + 1在复用连接上持续累加,而非每次从1开始 - 只影响
@var形式的用户变量,不影响DECLARE声明的存储过程局部变量 - ORM(如MyBatis、SQLAlchemy)若手动拼接
SET @x=...再执行查询,极易中招 - 解决不是“禁止用@变量”,而是明确控制其作用域:要么不用,要么显式重置
在执行关键查询前主动RESET SESSION不可取
RESET SESSION会清掉所有会话级设置,包括sql_mode、time_zone、autocommit等,破坏连接池预设状态,引发隐性兼容问题。
- 它不重置
@var——这是常见误解,RESET SESSION对用户变量无效 - 它会强制重置
character_set_client等连接参数,可能让后续INSERT乱码 - 某些MySQL版本(如5.7)执行后还会意外关闭prepared statement缓存
- 真要清变量,就该用
SET @var = NULL或SET @var = DEFAULT
安全重置用户变量的三种实操方式
核心原则:只动目标变量,不动全局会话状态。优先选最轻量、最明确的方式。
- 写死初始化:
SET @rownum = 0; SELECT @rownum := @rownum + 1 AS rank, name FROM users;—— 简单直接,但需每次手写 - 封装成函数(MySQL 5.7+):
CREATE FUNCTION init_counter() RETURNS INT BEGIN SET @counter = 0; RETURN 1; END,调用前先SELECT init_counter(); - 在应用层拦截:连接归还池前,执行
SET @var1 = NULL, @var2 = NULL(注意:必须是同一语句,避免多往返)
Go/Python里用database/sql或pymysql时的坑
这些驱动默认复用底层TCP连接,但不会帮你管@var。尤其当业务代码混用exec和query时,顺序错乱就会污染。
- Go中
db.Exec("SET @x = ?;", val)后立刻db.Query("SELECT @x")看似安全,但若中间有其他goroutine复用了同一连接,就失效 - Python的
pymysql若开启autocommit=False,事务内SET @x会在commit后仍存在 - 真正可靠的方案:把
SET和后续SELECT合并为一条multi-statement(需驱动支持且启用multi_statements=True),或彻底改用WITH RECURSIVE替代@var计数逻辑
@var,就得自己负责它的生与死。











