一、异步 SQLAlchemy
传统 SQLAlchemy 是同步的,在异步框架(如 FastAPI)中会阻塞事件循环。异步 SQLAlchemy 可以在 async 函数中直接 await 数据库操作。
安装
# 异步驱动
pip install asyncpg # PostgreSQL
pip install aiomysql # MySQL
pip install aiosqlite # SQLite
创建异步 Engine
from sqlalchemy.ext.asyncio import create_async_engine
engine = create_async_engine(
"postgresql+asyncpg://user:pass@localhost:5432/mydb",
echo=True,
pool_size=10,
max_overflow=20
)
连接字符串变化 :postgresql+psycopg2 → postgresql+asyncpg
创建异步 Session
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
AsyncSessionLocal = async_sessionmaker(
bind=engine,
class_=AsyncSession,
expire_on_commit=False
)
异步 CRUD
from sqlalchemy import select
async def get_users():
async with AsyncSessionLocal() as session:
result = await session.execute(select(User))
return result.scalars().all()
async def create_user(name: str):
async with AsyncSessionLocal() as session:
user = User(name=name)
session.add(user)
await session.commit()
await session.refresh(user)
return user
区别:
-
session.execute()→await session.execute() -
session.commit()→await session.commit() -
session.refresh()→await session.refresh()
异步预加载
from sqlalchemy.orm import selectinload
result = await session.execute(
select(User).options(selectinload(User.orders))
)
users = result.scalars().all()
二、连接池配置
连接池管理数据库连接,避免每次请求都新建连接。
常用参数
engine = create_engine(
"mysql+pymysql://user:pass@localhost:3306/mydb",
pool_size=10, # 连接池大小
max_overflow=20, # 超出 pool_size 后最多创建的连接数
pool_timeout=30, # 获取连接的超时时间(秒)
pool_recycle=3600, # 连接回收时间(秒),避免 MySQL 8 小时断连
pool_pre_ping=True, # 使用前检查连接是否有效
echo=False
)
| 参数 | 说明 | 建议值 |
|---|---|---|
pool_size |
连接池大小 | 10-20 |
max_overflow |
额外连接数 | pool_size 的 2 倍 |
pool_timeout |
获取连接超时 | 30 |
pool_recycle |
连接回收时间 | 3600(小于 MySQL 的 8 小时) |
pool_pre_ping |
使用前 ping | True |
连接池监控
# 查看连接池状态
print(engine.pool.status())
# 输出:Pool size: 10 Connections in pool: 2 Current Overflow: 0
三、与 FastAPI 集成
1. 项目结构
myapp/
├── main.py
├── database.py # 数据库配置
├── models.py # 模型定义
├── schemas.py # Pydantic 模型
├── crud.py # 数据库操作
└── routers/
└── users.py # 路由
2. database.py
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession
DATABASE_URL = "postgresql+asyncpg://user:pass@localhost:5432/mydb"
engine = create_async_engine(
DATABASE_URL,
pool_size=10,
max_overflow=20,
pool_pre_ping=True,
echo=False
)
AsyncSessionLocal = async_sessionmaker(
bind=engine,
class_=AsyncSession,
expire_on_commit=False
)
async def get_db():
"""依赖注入:每个请求一个 Session"""
async with AsyncSessionLocal() as session:
try:
yield session
finally:
await session.close()
3. models.py
from sqlalchemy import Column, Integer, String, DateTime
from sqlalchemy.orm import declarative_base
from datetime import datetime
Base = declarative_base()
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(50), nullable=False)
email = Column(String(100), unique=True)
created_at = Column(DateTime, default=datetime.now)
4. schemas.py
from pydantic import BaseModel, EmailStr
from datetime import datetime
class UserCreate(BaseModel):
name: str
email: EmailStr
class UserResponse(BaseModel):
id: int
name: str
email: str
created_at: datetime
class Config:
from_attributes = True # 支持从 ORM 对象转换
5. crud.py
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from models import User
from schemas import UserCreate
async def create_user(db: AsyncSession, user_data: UserCreate) -> User:
user = User(**user_data.model_dump())
db.add(user)
await db.commit()
await db.refresh(user)
return user
async def get_user(db: AsyncSession, user_id: int) -> User | None:
result = await db.execute(select(User).where(User.id == user_id))
return result.scalar_one_or_none()
async def get_users(db: AsyncSession, skip: int = 0, limit: int = 10):
result = await db.execute(select(User).offset(skip).limit(limit))
return result.scalars().all()
6. routers/users.py
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from database import get_db
from schemas import UserCreate, UserResponse
import crud
router = APIRouter(prefix="/users", tags=["users"])
@router.post("/", response_model=UserResponse)
async def create_user(user: UserCreate, db: AsyncSession = Depends(get_db)):
return await crud.create_user(db, user)
@router.get("/{user_id}", response_model=UserResponse)
async def get_user(user_id: int, db: AsyncSession = Depends(get_db)):
user = await crud.get_user(db, user_id)
if not user:
raise HTTPException(status_code=404, detail="User not found")
return user
@router.get("/", response_model=list[UserResponse])
async def list_users(skip: int = 0, limit: int = 10, db: AsyncSession = Depends(get_db)):
return await crud.get_users(db, skip, limit)
7. main.py
from fastapi import FastAPI
from contextlib import asynccontextmanager
from database import engine
from models import Base
from routers import users
@asynccontextmanager
async def lifespan(app: FastAPI):
# 启动时创建表
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
yield
# 关闭时释放连接
await engine.dispose()
app = FastAPI(lifespan=lifespan)
app.include_router(users.router)
@app.get("/")
async def root():
return {"message": "Hello World"}
8. 运行
pip install fastapi uvicorn sqlalchemy asyncpg pydantic[email]
uvicorn main:app --reload
