DECLARE变量线程安全且作用域严格限于单次触发器执行,不同会话、语句及行间完全隔离;并发错误主因是误用@用户变量或在集合操作中强行赋值给单变量。

不会。局部变量本身不会导致并发计算错误——问题出在误把局部变量当全局状态用,或者根本就没用对。
MySQL 中 DECLARE 变量是线程安全的,但作用域极短
MySQL 触发器里的 DECLARE 变量只在当前触发器执行上下文内有效,不同会话、不同 INSERT/UPDATE 语句、甚至同一语句的多行处理之间,彼此完全隔离。它不像 @var 那样跨语句残留,所以不存在“被其他事务覆盖”的并发污染。
- 每个触发器调用都拥有自己独立的一套
DECLARE变量,哪怕同时有 100 个 INSERT 并发进来,各自l_amount互不干扰 - 但注意:如果在
AFTER触发器里试图读取NEW.amount,会报错ERROR 1362 (HY000)——这不是变量问题,是 MySQL 禁止在 AFTER 里访问NEW的字段值 - 若在嵌套
BEGIN END块中重复声明同名变量(如外层DECLARE l_id INT,内层又DECLARE l_id VARCHAR(10)),内层会遮蔽外层,但仅限该块内;退出后外层变量照常可用
真正引发“并发计算错误”的其实是标量赋值 + 多行假设
错误不在变量声明,而在你用它承接了本不该承接的东西。比如写 SELECT @id = id FROM inserted(SQL Server)或 SET @total = (SELECT SUM(x) FROM t WHERE ...) 后再拿它去更新另一张表——这看似用了变量,实则把集合操作强行压成单值,破坏了 SQL 的天然并发安全性。
- 多行 INSERT 时,
SELECT @var = col FROM inserted只保留某一行的值(通常是最后扫描到的),其余行被静默丢弃 - 用这个“残缺”的
@var去驱动后续逻辑(如INSERT INTO log VALUES (@var)),结果就是只记录了一行,其余全漏 - 即使改用
DECLARE l_id INT,若仍写SELECT l_id = id FROM inserted,行为一样不可靠——MySQL 不允许这种语法,SQL Server 允许但语义危险
为什么有人觉得“局部变量也出错”?其实是混用了 @var 和 l_var
常见现场是:开发者在 BEFORE 触发器里用 DECLARE l_status VARCHAR(20) 存 NEW.status,看着没问题;但随后又顺手写了 SET @counter = @counter + 1 做计数,还把 @counter 当成“本次触发的累计值”来用。这就炸了。
-
l_status是干净的,每次触发都新声明、新赋值 -
@counter是会话级的,A 用户插入时加到 5,B 用户紧接着插入,@counter就从 5 变成 6,A 最后读到的可能是 6 而不是 5 - 调试时看到
SELECT @counter返回 6,就误以为“局部变量也串了”,其实只是混淆了命名和作用域
最易被忽略的一点:触发器里没有“本次插入的行数”这个原生概念。想按行数做分支(比如“如果插入超过 10 行就走异步路径”),不能靠变量计数,得查 (SELECT COUNT(*) FROM inserted) 或等价的集合表达式——变量永远只存一个值,而并发安全的依据,永远是集合操作本身的确定性,不是变量容器有多“局部”。

















