Files
backend/backend/database/db.py
2025-11-22 10:26:30 +08:00

122 lines
4.1 KiB
Python
Executable File

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
from typing import Annotated
from uuid import uuid4
from fastapi import Depends
from sqlalchemy import URL, text
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine, AsyncEngine
from sqlalchemy.orm import Session
from backend.common.log import log
from backend.common.model import MappedBase
from backend.core.conf import settings, get_db_uri
def create_async_engine_and_session(
url: str | URL,
echo: bool = False,
pool_size: int = 10,
max_overflow: int = 20,
pool_timeout: int = 30,
pool_recycle: int = 3600,
pool_pre_ping: bool = True,
application_name: str = "app"
) -> tuple[create_async_engine, async_sessionmaker[AsyncSession], async_sessionmaker[AsyncSession]]:
"""
创建 MySQL 异步引擎和会话工厂
参数优化说明:
- pool_size: 建议设置为 (核心数 * 2) + 有效磁盘数
- max_overflow: 峰值连接缓冲,避免连接风暴
- pool_recycle: 防止 MySQL 连接超时 (默认为 1 小时)
- pool_pre_ping: 强烈建议开启,处理连接失效问题
- application_name: 帮助 DBA 识别连接来源
"""
try:
# 创建异步引擎 (针对 MySQL 优化)
engine = create_async_engine(
url,
echo=echo,
echo_pool=echo,
future=True,
connect_args={
"charset": "utf8mb4", # MySQL 特定字符集
"autocommit": True, # 自动提交
"connect_timeout": 60, # 连接超时
},
pool_size=pool_size,
max_overflow=max_overflow,
pool_timeout=pool_timeout,
pool_recycle=pool_recycle,
pool_pre_ping=pool_pre_ping,
pool_use_lifo=True, # 使用 LIFO 提高连接池效率
# MySQL 特定优化参数
poolclass=None, # 使用默认 QueuePool
execution_options={
"isolation_level": "READ COMMITTED", # MySQL 推荐隔离级别
"compiled_cache": None # 禁用缓存,避免内存泄漏
}
)
background_engine = create_async_engine(
url,
pool_size=5,
max_overflow=10,
pool_pre_ping=True,
pool_recycle=300,
connect_args={
"charset": "utf8mb4",
"autocommit": True,
"connect_timeout": 60,
}
)
except Exception as e:
log.error(f'❌ MySQL 数据库连接失败: {e}')
sys.exit(1)
else:
# 创建异步会话工厂 (针对 MySQL 优化)
db_session = async_sessionmaker(
bind=engine,
autoflush=False,
expire_on_commit=False,
# MySQL 特定优化
class_=AsyncSession,
twophase=False, # 禁用两阶段提交
enable_baked_queries=False, # 禁用 baked 查询避免内存问题
info={"app_name": application_name} # 添加应用标识
)
background_db_session = async_sessionmaker(
background_engine,
expire_on_commit=False,
autoflush=False
)
log.info(f'✅ MySQL 异步引擎创建成功 | 连接池: [{pool_size}] - [{max_overflow}]')
return engine, db_session, background_db_session
async def get_db():
"""session 生成器"""
async with async_db_session() as session:
yield session
async def create_table() -> None:
"""创建数据库表"""
async with async_engine.begin() as coon:
await coon.run_sync(MappedBase.metadata.create_all)
def uuid4_str() -> str:
"""数据库引擎 UUID 类型兼容性解决方案"""
return str(uuid4())
SQLALCHEMY_DATABASE_URL = get_db_uri(settings)
async_engine, async_db_session, background_db_session = create_async_engine_and_session(SQLALCHEMY_DATABASE_URL)
# Session Annotated
CurrentSession = Annotated[AsyncSession, Depends(get_db)]