是,会锁表且极危险;InnoDB下BEFORE INSERT中执行SELECT COUNT(*)会加一致性读锁,易致死锁或MVCC偏差,推荐用SIGNAL抛异常或维护独立计数器表。

BEFORE INSERT 触发器里 COUNT(\*) 会锁表吗
会,而且很危险。在 BEFORE INSERT 触发器中执行 SELECT COUNT(*) FROM table_name,MySQL(尤其是 InnoDB)会对整张表加 SHARE MODE 或更重的锁,高并发插入时极易引发锁等待甚至死锁。这不是理论风险,而是线上真实踩过的坑。
实操建议:
- 避免在触发器里做全表扫描类聚合,
COUNT(*)就是典型 - 改用维护一个独立计数器表(如
table_row_count),每次 INSERT/DELETE 后用UPDATE ... SET cnt = cnt + 1原子更新 - 若必须用触发器判断,优先查主键自增最大值(
SELECT AUTO_INCREMENT FROM information_schema.TABLES),但仅适用于无 DELETE 场景
用变量+INSERT IGNORE绕过触发器行数检查的漏洞
很多人写完触发器后测试发现:用 INSERT IGNORE 或 REPLACE INTO 能绕过 BEFORE INSERT 的拦截逻辑——因为 MySQL 对这些语句的触发器执行时机和条件判断有特殊处理,不是每条“逻辑插入”都会进触发器。
正确做法:
- 统一用普通
INSERT,禁用IGNORE/REPLACE权限 - 在触发器里显式检查
NEW是否已存在(例如查唯一键),再决定是否SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'max rows exceeded' - 若业务允许覆盖,把“限制行数”逻辑移到应用层或存储过程,由事务包裹计数+插入两步
PostgreSQL 中 BEFORE INSERT 触发器统计行数的替代方案
PostgreSQL 不支持在触发器中直接修改同一张表(会报 cannot modify table "t" within its own trigger function),所以也不能靠 SELECT COUNT(*) 然后 RAISE EXCEPTION 安全拦截——因为查询本身不阻塞,但后续插入仍可能并发通过。
可行路径:
- 用
pg_advisory_xact_lock(hashint4(table_oid))手动加事务级轻量锁,再查行数并判断 - 建一张
table_stats表,用AFTER INSERT和AFTER DELETE触发器维护实时计数,BEFORE INSERT只查这张小表 - 更推荐:用分区表 +
CHECK约束控制单分区行数,比触发器更高效可靠
真正稳定的行数限制:别只靠触发器
触发器只是“最后一道闸门”,它解决不了竞争条件、跨会话一致性、DDL 变更等根本问题。比如两个会话同时查到当前 99 行,都允许插入第 100 行,结果变成 101 行。
生产环境该怎么做:
- 用带 CAS(Compare-And-Swap)语义的应用层控制:先
SELECT COUNT(*),再INSERT ... SELECT ... WHERE (SELECT COUNT(*) FROM t) < 100(需事务+隔离级别为REPEATABLE READ或更高) - 对强一致性要求场景,用 Redis INCR 做分布式计数器,INSERT 前
INCR并判断返回值 - 接受“软限制”时,定期跑清理作业(如
DELETE FROM t ORDER BY id LIMIT 10),比实时拦截更可控
触发器适合做兜底校验,但不能当作唯一防线。行数限制本质是状态同步问题,而数据库触发器天生不适合处理跨事务状态。

















