触发器无法限制并发会话数,因连接建立早于sql执行,且断开无对应触发点;必须通过performance_schema.threads实时统计+应用层检查实现。

触发器无法直接限制并发操作次数,必须配合会话级状态表 + 应用层协作;单纯靠触发器计数极易漂移、漏减、超限。
为什么不能只靠 INSERT/UPDATE 触发器统计活跃会话
MySQL 的触发器在 DML 执行时才触发,而用户会话(Session)的建立、断开完全独立于 SQL 执行流。常见错误包括:
- 在
users表上建BEFORE INSERT触发器想“拦登录” → 实际上连接已建立,SQL 还没跑,触发器根本没机会执行 - 用
AFTER INSERT往session_count表写 +1,但连接异常断开(如网络中断、客户端崩溃)→ 没有对应 DELETE 或 UPDATE 触发点,计数永远卡住 - 多个并发请求同时插入,查当前计数再 +1 → 经典 check-then-act 竞态,两个会话都读到 2,都写入 3,实际变成 4 个活跃会话
CURRENT_USER() 是唯一可信的会话标识,但有使用陷阱
触发器内必须用 CURRENT_USER() 获取认证账号,它返回「用户名@host」格式,且不可被应用伪造。注意这些坑:
- 不要赋值给变量再比较:
SET @u = CURRENT_USER(); IF @u = 'alice' THEN ...→ 可能因隐式字符集转换导致匹配失败 - 不要混用
USER():它返回客户端声明的账号,代理层或连接池下常为proxy@%,失去用户粒度 - 如果所有业务都用统一账号(如
app_rw@'%'),CURRENT_USER()就退化成无意义常量 → 此时必须回到应用层传X-User-ID或改用中间件识别逻辑用户
如何让计数表不漂移:必须配锁 + 清理机制
若坚持用触发器维护计数,需满足三个条件才能勉强可控:
- 计数更新必须原子:用
UPDATE user_session_cnt SET cnt = cnt + 1 WHERE user_name = CURRENT_USER() AND cnt ,配合行锁(<code>SELECT ... FOR UPDATE)或应用锁(如sp_getapplock)包裹读-判-改 - 必须配套
BEFORE DELETE触发器(针对 session_log 表)或定时任务:每 30 秒扫描performance_schema.threads,对已消失的PROCESSLIST_ID主动减 1 - 禁止在触发器里查
information_schema.PROCESSLIST:默认权限不足,且该视图在 MySQL 8.0+ 中已被标记为 deprecated;应改用performance_schema.threads并提前授权:GRANT SELECT ON performance_schema.threads TO 'app_user'@'%'
更稳妥的做法:别用触发器管会话生命周期
真正稳定的方案是放弃持久化计数表,每次关键操作前实时查系统表:
- 执行
SELECT COUNT(*) FROM performance_schema.threads WHERE PROCESSLIST_USER = CURRENT_USER() AND TYPE = 'FOREGROUND' AND PROCESSLIST_STATE IS NOT NULL - 结果 > 上限就直接
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Too many concurrent sessions' - 这个检查必须放在存储过程开头、或应用 DAO 层最外层,不能藏在触发器里——因为触发器晚于连接建立,也晚于事务开启
复杂点在于:你得确保每次查询都走同一套权限上下文,且 performance_schema.threads 的刷新延迟(通常










