触发器不能直接监听登录行为,因其仅响应insert/update/delete等dml操作,而登录属于认证阶段的连接事件,不涉及表操作;登录失败等事件需由应用层主动记录并判断阈值,再显式更新锁定状态。

触发器不能直接监听登录行为
SQL 触发器(TRIGGER)只能响应 INSERT、UPDATE、DELETE 等 DML 操作,而用户登录是数据库连接建立过程,属于认证阶段,不经过表操作,因此无法用标准触发器捕获“登录失败”事件。
这意味着你不能在 users 表上建一个 BEFORE INSERT 或类似触发器来拦截密码错误——那根本不会触发。
常见误操作包括:试图在 login_attempts 表上建触发器自动更新 locked_at,却忽略该表本身必须由应用层主动写入;或误以为 ON FAILED LOGIN 是通用语法(仅 Oracle 有类似审计功能,且非触发器)。
必须由应用层记录并检查失败次数
真正可行的路径是:应用在每次登录验证失败后,向数据库插入一条记录到 login_attempts 表,并由应用自己查询最近 1 小时(或自定义窗口)内该用户的失败次数。达到阈值(如 5 次)后,再执行 UPDATE users SET locked_at = NOW() WHERE id = ?。
关键点:
-
login_attempts表需包含user_id、attempted_at(DATETIME或TIMESTAMP)、success(BOOLEAN或TINYINT) - 查询失败次数应带时间窗口,避免永久累积:
SELECT COUNT(*) FROM login_attempts WHERE user_id = 123 AND success = 0 AND attempted_at > DATE_SUB(NOW(), INTERVAL 1 HOUR) - 不要依赖触发器自动锁定——应用必须显式判断并更新
locked_at字段 - 登录逻辑中,需在密码校验前先查
locked_at IS NOT NULL AND locked_at > DATE_SUB(NOW(), INTERVAL 24 HOUR)(按需调整解锁时长)
PostgreSQL / MySQL 中可辅助实现的机制
虽然触发器不能监听登录,但可以用于保障数据一致性,比如防止手动绕过锁定状态:
- 在
users表上建BEFORE UPDATE触发器,当locked_at非空时,拒绝将password_hash设为空或设为旧值(防暴力重置) - 在
login_attempts表上建BEFORE INSERT触发器,自动填充attempted_at:NEW.attempted_at := NOW() - MySQL 8.0+ 可配合
EVENT定期清理过期的login_attempts记录,但不能替代应用逻辑
注意:EVENT 不是触发器,也不实时;PostgreSQL 则需用 pg_cron 扩展,同样只是辅助。
真正需要锁定时,别只靠字段标记
仅在 users.locked_at 写个时间戳是脆弱的:应用可能忘记检查、前端可能缓存了旧 token、甚至 DBA 直接改表绕过逻辑。
更稳妥的做法包括:
- 应用层登录接口返回明确错误码(如
423 Locked),并附带retry_after时间 - 结合 Redis 做分布式计数(
INCR login_fail:123+EXPIRE),比查表更快、更原子 - 数据库层面,MySQL 可用
CREATE USER ... ACCOUNT LOCK(8.0+),但这是 DB 用户级锁定,和业务用户无关;PostgreSQL 无等效命令
最常被忽略的是:解锁机制。没人会手动清 locked_at,必须有定时任务或用户自助解封流程——而这部分完全不在 SQL 能力范围内。










