贴吧式
应用侧超时和数据库侧超时到底谁是最终裁判?聊聊两套超时体系的取舍
帖子正文与楼层回复。
楼主 陈砚舟-102
数据库超时重试慢查询DBAMySQL
背景
社区里已经有一篇《超时与重试:高并发后端最容易翻车、却最少被认真讨论的两件事》把「超时不是运维项」讲得很透了,我基本认同。但最近带团队治理一批慢查询时,我发现一个被绕过去的问题:应用侧的超时(HikariCP connectionTimeout / socketTimeout、Spring @Transactional 超时、HTTP 客户端 read timeout)和数据库侧的超时(wait_timeout、innodb_lock_wait_timeout、max_execution_time、KILL 机制)经常互相打架,而团队里没有人系统地讲清楚「到底听谁的」。
举三个我最近撞上的场景:
- 应用 socketTimeout=3s,但数据库侧
innodb_lock_wait_timeout=50s。结果应用端早就抛异常、连接回池、业务返回 500 给前端了,但那条 SQL 还在数据库里继续跑、继续持有锁,直到 50s 后被锁等待者或 DBA 手动 KILL。用户看到的是「超时」,数据库看到的是「还在执行」——两边状态完全脱节。 - 反过来,
max_execution_time=5s把一条本应跑 8s 的报表查询杀了,业务侧根本没配超时,拿到的是ERROR 3024,前端直接白屏。 - 事务内多条 SQL,应用侧
@Transactional(timeout=10)按「事务开始」算,但数据库每条 SQL 各自有max_execution_time,两边口径不一致,超时点完全对不上。
常见分歧 / 几种做法
- 应用侧裁判派:数据库不该有超时(或调大到 300s),一切以应用侧为准,理由是可观测、可降级、可重试;数据库侧超时是「黑盒」,出问题只能看 slow log 猜。
- 数据库侧裁判派:应用侧超时是「放弃」,不是「终止」——连接回池、事务回滚,但 SQL 可能还在跑,锁还在持;只有数据库侧 KILL 才真正终止。所以关键链路上,数据库侧必须有硬超时兜底。
- 双侧对齐派:两边都要配,且要满足
应用超时 < 数据库单条 SQL 超时 < 事务超时 < 锁等待超时,形成漏斗;但实操里没人真的维护这套不等式,配完就忘。 - DBA 兜底派:平时都不配,靠
performance_schema+ 慢日志 + 定期巡检 KILL 长查询,把超时当成「事后治理」而非「事前约束」。
我的倾向与理由
我倾向 数据库侧做硬兜底、应用侧做软超时 的组合,理由有三:
- 应用侧超时只能「放弃等待」,不能「终止执行」,对锁持有和 IO 占用毫无约束;
- 数据库侧
max_execution_time是 per-SQL 的,粒度比事务超时更细,且对 KILL 的语义是原子的; - 应用侧超时的价值在于「快速失败 + 降级」,数据库侧超时的价值在于「保护数据库自身」,两者职责本来就不一样,不该互相替代。
但我不确定的是:重试策略应该挂在应用侧还是数据库侧? 我目前的默认是「数据库侧不重试、应用侧幂等重试」,但见过有人把重试塞进存储过程里,也见过有人在 ORM 层做无脑 retry 把锁等待打满的。
想听大家在什么场景下怎么选
- 你的关键链路上,应用侧超时和数据库侧超时的具体值分别是多少?谁先触发?
- 有没有遇到过「应用超时但 SQL 还在跑」导致锁堆积的情况?最后怎么解的?
max_execution_time你建议默认开吗?开了之后报表查询怎么兼容?- 重试策略你挂在应用侧、数据库侧、还是中间件(比如 ShardingSphere / ProxySQL)?
- 你们团队有没有维护「应用超时 < SQL 超时 < 锁等待 < 事务超时」这套不等式?靠什么工具保证它不被改坏?
欢迎贴具体的配置片段和踩坑记录,越具体越好,我们按场景聊,不聊抽象原则。
赞 0 · 收藏 0 · 楼层 0
楼层
还没有人回复。