sqlalchemy慢查询日志默认不生效,因sqlalchemy_echo仅输出所有sql而不判断耗时;需用event.listen()监听before_cursor_execute和after_cursor_execute事件,通过context传递起始时间,超1秒记warn日志,且必须在db.init_app(app)之后注册。

SQLAlchemy慢查询日志为什么默认不生效
Flask本身不控制SQLAlchemy的日志开关,SQLALCHEMY_ECHO只打所有SQL(含参数展开),但不区分快慢;真正捕获“慢查询”靠的是SQLAlchemy的engine.connect().execution_options()之外的钩子机制——必须手动注册before_cursor_execute或用event.listen()监听执行耗时。
怎么配置1秒以上SQL自动记WARN日志
在创建db实例后、应用启动前插入事件监听。注意:不能放在create_app()外层模块级执行,否则Flask-SQLAlchemy可能尚未绑定引擎。
from sqlalchemy import event
from sqlalchemy.engine import Engine
import time
import logging
<p>logger = logging.getLogger('sqlalchemy.slow')</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill4102" title="Shadows Python Sensei"><img
src="https://img.php.cn/upload/skill/000/000/081/178990406882325.jpg" alt="Shadows Python Sensei" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/skill4102" title="Shadows Python Sensei" class="overflowclass">Shadows Python Sensei</a>
<p class="overflowclass">Python 最佳实践助手——代码规范、设计模式、性能优化、测试与类型注解。适用于编写或审查 Python 代码。</p>
</div>
<a rel="nofollow" href="/xiazai/skill4102" title="Shadows Python Sensei" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div><p>@event.listens_for(Engine, "before_cursor_execute")
def before_cursor_execute(conn, cursor, statement, parameters, context, executemany):
context._query_start_time = time.time()</p><p>@event.listens_for(Engine, "after_cursor_execute")
def after_cursor_execute(conn, cursor, statement, parameters, context, executemany):
total = time.time() - context._query_start_time
if total > 1.0: # 慢阈值设为1秒
logger.warning(
"Slow query (%.3fs): %s | params: %r",
total, statement.strip()[:200], parameters[:5]
)
</p>
- 必须监听
before_cursor_execute和after_cursor_execute成对事件,context是唯一可跨事件传参的载体 -
executemany=True场景下parameters是列表,取[:5]防日志爆炸 - 日志级别用
WARNING,避免被INFO淹没;建议单独配sqlalchemy.slowlogger到文件
Flask-SQLAlchemy里怎么拿到底层Engine
db.engine就是真实Engine对象,但需确保db.init_app(app)已调用完毕。常见错误是监听代码写在db = SQLAlchemy()之后、init_app()之前,此时db.engine还是None。
- 正确时机:在
create_app()内,db.init_app(app)之后立即注册事件 - 如果用工厂模式,别把监听逻辑写进
__init__.py顶层,那里app还没创建 - 验证是否生效:
print(db.engine.url)不报错,且event.listens_for无异常即表示绑定成功
为什么有些慢查询日志没打出来
根本原因在于SQLAlchemy的执行路径分叉:ORM查询(如session.query(User).filter(...))走Session.execute,而原生db.session.execute(text("..."))或db.engine.execute(...)(已弃用)走不同分支。v2.0+后统一归入Connection.execute,但before_cursor_execute仍只覆盖底层驱动执行,不覆盖ORM-level缓存命中、关系预加载(joinedload)等间接开销。
- ORM懒加载触发的SQL可能不经过该监听——它由
Query内部触发,绕过cursor层级 - 事务内批量操作(
session.bulk_insert_mappings)使用executemany,但参数被截断,需结合echo=True交叉验证 - 真正想定位瓶颈,得配合
flask-profiler或sqltap做全链路采样,单靠慢日志只能抓显性长SQL
实际部署时,慢阈值别死守1秒——高并发服务里0.3秒的简单JOIN都可能成为瓶颈,关键看P95响应时间和DB负载。日志格式里保留statement.strip()[:200]很必要,太长的SQL会冲垮日志系统,也看不出关键WHERE条件。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










