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

Gevent Worker被SQLAlchemy查询阻塞问题求助

问题分析与解决

问题现象

使用Flask开发应用,通过Gunicorn搭配1个Gevent Worker部署,采用SQLAlchemy结合mysql-connector连接MySQL数据库。当执行耗时较长的查询时,同时发起两个API调用,首个查询超时会导致Worker阻塞或终止,必须等待数据库响应完成后才能处理第二个请求。

依赖版本(最新版本仍存在该问题)

flask = "2.0.3"
sqlalchemy = "1.4.39"
mysql-connector-python = "8.0.29"
gunicorn = "20.1.0"
gevent = "22.10.2"

主程序代码(main.py)

from flask import Flask
from sqlalchemy import create_engine
from sqlalchemy.orm import Session, scoped_session, sessionmaker


app = Flask(__name__)

@app.route("/")
def hello():
    print("!!!! GET REQUEST !!!!")

    db_url = "mysql+mysqlconnector://username:password@host:3306/db_name"

    engine = create_engine(db_url)

    session_maker = sessionmaker(bind=engine)
    session: Session = scoped_session(session_maker)

    sql_string = """
        select *
        from big_table
        inner join another_big_table
        group by big_table.id;
    """

    result = session.execute(sql_string)
    result = dict(result.fetchall())

    session.close()

    print(result)
    print("!!!! FINISH REQUEST !!!!")

    return ""

服务启动命令

gunicorn -k gevent -w 1 -b 127.0.0.1:11111 main:app

API调用命令

curl localhost:11111

问题根源与解决办法

根源

Gevent通过协程实现并发,但mysql-connector-python是同步阻塞的数据库驱动,执行耗时查询时会占用Gevent Worker的唯一线程,导致后续请求被阻塞。即使设置了查询超时,同步驱动的阻塞行为也无法被Gevent的协程调度器中断,必须等待数据库返回结果(或超时响应)才能释放线程处理下一个请求。

解决措施

  1. 改用异步数据库驱动
    替换mysql-connector-python为支持异步的aiomysql,配合SQLAlchemy的异步模式使用:

    • 安装依赖:pip install aiomysql sqlalchemy[asyncio]
    • 修改代码适配异步逻辑:
      from flask import Flask
      from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
      from sqlalchemy.orm import sessionmaker
      
      app = Flask(__name__)
      
      # 全局初始化异步引擎(不要在请求内重复创建)
      engine = create_async_engine("mysql+aiomysql://username:password@host:3306/db_name")
      AsyncSessionLocal = sessionmaker(bind=engine, class_=AsyncSession, expire_on_commit=False)
      
      @app.route("/")
      async def hello():
          print("!!!! GET REQUEST !!!!")
          async with AsyncSessionLocal() as session:
              sql_string = """
                  select *
                  from big_table
                  inner join another_big_table
                  group by big_table.id;
              """
              result = await session.execute(sql_string)
              result = dict(result.fetchall())
          print(result)
          print("!!!! FINISH REQUEST !!!!")
          return ""
      
  2. 给同步驱动打Gevent补丁
    如果不想切换异步驱动,可在应用启动时给mysql-connector的阻塞IO打补丁,让Gevent接管其阻塞调用:

    • 在main.py开头添加:
      from gevent import monkey
      monkey.patch_all()
      

    注意:这种方式对部分同步驱动的兼容性可能存在问题,需测试验证。

  3. 调整Worker数量
    增加Gunicorn的Worker进程数(比如-w 2),一个Worker被阻塞时,另一个Worker可处理后续请求,但这只是缓解方案,无法解决单Worker下的阻塞问题。

  4. 优化查询本身
    耗时查询是问题根源,优化SQL语句(添加索引、避免全表扫描、优化分组逻辑)或限制返回数据量,减少查询执行时间,从根本上降低阻塞风险。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 04:17:16