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的协程调度器中断,必须等待数据库返回结果(或超时响应)才能释放线程处理下一个请求。
解决措施
改用异步数据库驱动
替换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 ""
- 安装依赖:
给同步驱动打Gevent补丁
如果不想切换异步驱动,可在应用启动时给mysql-connector的阻塞IO打补丁,让Gevent接管其阻塞调用:- 在main.py开头添加:
from gevent import monkey monkey.patch_all()
注意:这种方式对部分同步驱动的兼容性可能存在问题,需测试验证。
- 在main.py开头添加:
调整Worker数量
增加Gunicorn的Worker进程数(比如-w 2),一个Worker被阻塞时,另一个Worker可处理后续请求,但这只是缓解方案,无法解决单Worker下的阻塞问题。优化查询本身
耗时查询是问题根源,优化SQL语句(添加索引、避免全表扫描、优化分组逻辑)或限制返回数据量,减少查询执行时间,从根本上降低阻塞风险。
内容的提问来源于stack exchange,提问作者Scottie
相关产品推荐
相关产品推荐

