“broken pipe”总在8小时后出现是因为mysql服务端默认wait_timeout为28800秒(8小时),超时后主动关闭空闲连接,而sqlalchemy仍持失效socket句柄执行rollback/commit导致;根治方案是启用pool_pre_ping=true实现每次借连接前select 1探活,或设pool_recycle为wait_timeout-60确保连接及时回收。

为什么“Broken pipe”总在8小时后出现
这不是 SQLAlchemy 或你的代码写错了,而是 MySQL 服务端主动断开了空闲连接。默认 wait_timeout 是 28800 秒(8 小时),超时后 MySQL 会 kill 掉这个连接,但 SQLAlchemy 的 Session 还拿着旧的 socket 句柄继续发 ROLLBACK 或 COMMIT,系统就报 BrokenPipeError(32, 'Broken pipe')。
关键点在于:连接池里存的不是“活连接”,而是“可能已失效的句柄”。你没用它时它悄悄死了,你一用就炸。
必须开启连接有效性验证(test_on_borrow)
靠手动 session.close() 或 try-except 重试只能缓解,不能根治。真正要做的,是让连接池在每次借出连接前,先 ping 一下它还活着没。
-
pool_pre_ping=True(SQLAlchemy 1.3+ 推荐):每次从连接池取连接前自动执行SELECT 1,失败则丢弃该连接、重试下一个 - 如果用的是老版本 SQLAlchemy,得配
pool_recycle=3600(比 wait_timeout 小至少 60 秒),强制连接复用不超过 1 小时 - 别依赖
pool_use_lifo或max_overflow来绕开问题——它们不解决连接死亡本身
别忽略 MySQL 服务端配置同步
只调 SQLAlchemy 没用。如果 DBA 把 wait_timeout 改成 7200(2 小时),而你 pool_recycle 还设 3600,照样崩。
- 查当前值:
SHOW VARIABLES LIKE 'wait_timeout'; - 设连接池回收间隔为
wait_timeout - 60,比如服务端是 7200,你就设pool_recycle=7140 - 如果无法改服务端,
pool_pre_ping=True是唯一稳妥方案——它不依赖时间预估,只认实时连通性
Flask-SQLAlchemy 为什么“好像没事”
不是它真没事,而是它默认启用了 pool_pre_ping=True,并且把 SQLALCHEMY_ENGINE_OPTIONS 默认塞进了常见健壮参数。如果你手写 create_engine 却没显式加这个,就会掉坑里。
最容易被忽略的一点:pool_pre_ping 在高并发下会轻微增加延迟(毕竟每次都要多一次 round-trip),但它换来的是确定性——你不再需要猜“这次崩不崩”。生产环境宁可慢一点,也不要半夜被报警叫醒修连接。











