You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在FastAPI+SQLAlchemy中收集单HTTP请求的所有数据库查询?

解决FastAPI+SQLAlchemy请求内数据库查询统计与告警问题

别从日志里费劲提取了,用SQLAlchemy的事件监听机制结合FastAPI的请求上下文,就能精准统计单个请求内的所有数据库查询,简单又靠谱。

核心思路

利用SQLAlchemy的执行事件(同步用after_execute、异步用after_execute_async),在每次数据库查询完成后,把查询语句、参数等信息绑定到当前请求的专属存储中;请求结束时统一统计查询数量,超过阈值就触发告警。

实现代码

1. 同步SQLAlchemy引擎示例

from fastapi import FastAPI, Request
from sqlalchemy import create_engine
from sqlalchemy.engine import Engine
from sqlalchemy.event import listens_for

app = FastAPI()

# 初始化同步数据库引擎
engine = create_engine("postgresql://user:password@localhost/db_name")

# 告警逻辑:替换成你的通知方式(邮件、企业微信等)
def send_alert(request_path: str, query_count: int, queries: list):
    print(f"⚠️ 告警:请求 {request_path} 执行了 {query_count} 次查询,超过阈值!")
    print("查询详情:")
    for idx, q in enumerate(queries, 1):
        print(f"{idx}. 语句: {q['statement']} | 参数: {q['parameters']}")

# 注册SQLAlchemy查询完成事件监听
@listens_for(Engine, "after_execute")
def track_query(conn, clauseelement, multiparams, params, result, execution_options):
    # 从数据库连接中获取当前FastAPI请求对象
    request = getattr(conn, "_fastapi_request", None)
    if not request:
        return
    
    # 记录查询信息到请求的state中
    query_info = {
        "statement": str(clauseelement),
        "parameters": multiparams or params,
        "execution_time": result.execution_time if hasattr(result, "execution_time") else 0
    }
    if not hasattr(request.state, "queries"):
        request.state.queries = []
    request.state.queries.append(query_info)

# FastAPI中间件:绑定请求到数据库连接,请求结束后统计查询
@app.middleware("http")
async def bind_request_to_db(request: Request, call_next):
    with engine.connect() as conn:
        # 将当前请求对象附加到数据库连接上
        setattr(conn, "_fastapi_request", request)
        # 处理请求
        response = await call_next(request)
        # 统计并判断是否告警
        query_count = len(getattr(request.state, "queries", []))
        THRESHOLD = 10  # 自定义查询数量阈值
        if query_count > THRESHOLD:
            send_alert(request.url.path, query_count, request.state.queries)
    return response

2. 异步SQLAlchemy引擎适配

如果使用异步引擎,只需要调整事件监听和中间件逻辑:

from sqlalchemy.ext.asyncio import create_async_engine
from sqlalchemy.ext.asyncio.engine import AsyncEngine

# 初始化异步数据库引擎
async_engine = create_async_engine("postgresql+asyncpg://user:password@localhost/db_name")

# 异步事件监听
@listens_for(AsyncEngine, "after_execute_async")
async def track_async_query(conn, clauseelement, multiparams, params, result, execution_options):
    request = getattr(conn, "_fastapi_request", None)
    if not request:
        return
    
    query_info = {
        "statement": str(clauseelement),
        "parameters": multiparams or params,
        "execution_time": result.execution_time if hasattr(result, "execution_time") else 0
    }
    if not hasattr(request.state, "queries"):
        request.state.queries = []
    request.state.queries.append(query_info)

# 异步中间件
@app.middleware("http")
async def bind_request_to_async_db(request: Request, call_next):
    async with async_engine.connect() as conn:
        setattr(conn, "_fastapi_request", request)
        response = await call_next(request)
        query_count = len(getattr(request.state, "queries", []))
        THRESHOLD = 10
        if query_count > THRESHOLD:
            await send_alert(request.url.path, query_count, request.state.queries)  # 告警函数需改为异步
    return response

关键优势

  • 精准度高:直接从SQLAlchemy执行链路获取查询数据,不受日志格式、日志级别限制
  • 请求隔离:每个请求的查询独立统计,不会和其他请求的查询混淆
  • 扩展性强:可额外记录执行时间、参数等信息,方便后续排查n+1问题

额外优化建议

统计到异常请求后,用SQLAlchemy的selectinload或joinedload预加载关联数据,直接解决n+1问题;同时给高频查询添加合适的索引,优化整体性能。

内容的提问来源于stack exchange,提问作者Nacho

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 13:35:14