跳转至

后端-ORM-execution_options在asyncmy中返回coroutine导致500

问题现象

  • 网页新闻列表页 /#/web-news 显示空白,无任何数据
  • 浏览器控制台报错:GET /api/web-crawler/news?page=1&page_size=20 500 (Internal Server Error)
  • 前端日志:AxiosError: Request failed with status code 500

核心问题

  • 根因SQLAlchemy AsyncConnection.execution_options(isolation_level="SERIALIZABLE") 在 asyncmy (MySQL 异步驱动) 中返回 coroutine 对象而非 AsyncConnection,导致后续 conn.begin() 调用报错:
    AttributeError: 'coroutine' object has no attribute 'begin'
    
  • 触发代码modules/web-crawler/service.py:155-158):
    conn = await session.connection()
    conn = conn.execution_options(isolation_level="SERIALIZABLE")  # asyncmy 下返回 coroutine
    async with conn.begin():  # 此处崩溃
    
  • 与 SQLite 的差异:本地开发用 SQLite + aiosqlite 驱动,相同代码正常工作;服务器用 MySQL + asyncmy 驱�动,execution_options 行为不同
  • 数据库配置:服务器 DB_HOST=localhostDB_PORT=3306、MySQL
  • 影响 commit2b2ad7e(前一个修复引入的问题)

解决方法

  1. 去掉 execution_options + conn.begin() 包装,恢复使用 session.execute() 直接查询
  2. 保留 order_by(desc(WebNews.id))id 不可变排序键是解决分页漂移的核心修复,不依赖事务隔离级别
  3. MySQL 默认 REPEATABLE READ 隔离级别已保证同一事务内读取一致性

修复后的代码modules/web-crawler/service.py):

# 直接使用 session.execute(),无需 execution_options
count_stmt = select(func.count()).select_from(base_stmt.subquery())
total_result = await session.execute(count_stmt)
total = total_result.scalar() or 0

stmt = base_stmt.order_by(desc(WebNews.id))
stmt = stmt.offset((page - 1) * page_size).limit(page_size)
result = await session.execute(stmt)
items = list(result.scalars().all())

  1. 部署:git push → 服务器 git pullsystemctl restart news-backend

下次防错措施

  • 快速定位:查看 journalctl -u news-backend -n 20 搜索 execution_optionscoroutine 关键字
  • 驱动兼容性检查:SQLAlchemy ORM 涉及 execution_options 的代码需在 MySQL + asyncmy 环境下验证,不能仅依赖 SQLite 本地测试
  • 部署前建议:在 start.bat 或部署脚本中增加 curl http://localhost:8000/api/web-crawler/news?page=1&page_size=1 的冒烟测试
  • 代码规范:避免在异步 Session 上使用 session.connection() + conn.begin() 嵌套事务;Session 自身的自动事务管理通常已足够