"""数据库连接管理。 **为什么用同步引擎**:本项目并发极低(内部系统,营销人员数十人), 异步拿不到吞吐收益;而重活——绩效批量核算、pandas 报表、Excel 导出—— 全是同步阻塞代码,放在异步路由里会卡死事件循环,且这类问题在开发期 低并发下几乎不暴露。改用同步后,路由写 `def`,FastAPI 会**自动放入 线程池执行**,天然规避上述陷阱,也不会阻塞事件循环。 引擎采用**延迟创建**:应用启动时不连接 MySQL, 这样 Docker 未启动时仍能运行服务、查看接口文档。 """ from collections.abc import Generator from sqlalchemy import Engine, create_engine from sqlalchemy.orm import Session, sessionmaker from app.core.config import get_settings _engine: Engine | None = None _session_factory: sessionmaker[Session] | None = None def get_engine() -> Engine: """获取同步引擎单例(首次调用时创建)。""" global _engine, _session_factory if _engine is None: settings = get_settings() _engine = create_engine( settings.database_url, # 连接池中的连接在使用前先探活,避免 MySQL 重启后取到死连接 pool_pre_ping=True, pool_recycle=3600, echo=False, ) _session_factory = sessionmaker(_engine, expire_on_commit=False) return _engine def get_session_factory() -> sessionmaker[Session]: """获取会话工厂单例。""" get_engine() assert _session_factory is not None # get_engine 已确保初始化 return _session_factory def get_db() -> Generator[Session, None, None]: """FastAPI 依赖:提供一个数据库会话。 只负责会话的创建与关闭,**事务边界由 service 层控制** —— 这样一次请求内的多个仓储调用可以共处同一事务。 """ factory = get_session_factory() with factory() as session: yield session def dispose_engine() -> None: """关闭连接池。应用退出时调用。""" global _engine, _session_factory if _engine is not None: _engine.dispose() _engine = None _session_factory = None