Python 数据库进阶:SQLAlchemy ORM 高级查询、性能优化、事务管理
Introduction
SQLAlchemy 是 Python 最强大的 ORM 库。本文讲解关系定义、一对多/多对多查询、懒加载vs急加载、批量操作、性能优化(索引、查询优化)、事务管理和连接池配置。
---
模型关系定义
from sqlalchemy import Column, Integer, String, Text, ForeignKey, DateTime, Boolean
from sqlalchemy.orm import relationship, declarative_base
from datetime import datetime
Base = declarative_base()
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
username = Column(String(50), unique=True, nullable=False)
email = Column(String(120), unique=True)
posts = relationship('Post', back_populates='author', cascade='all, delete-orphan')
tags = relationship('Tag', secondary=user_tags, back_populates='users')
class Post(Base):
__tablename__ = 'posts'
id = Column(Integer, primary_key=True)
title = Column(String(200), nullable=False)
content = Column(Text)
author_id = Column(Integer, ForeignKey('users.id'))
author = relationship('User', back_populates='posts')
comments = relationship('Comment', back_populates='post', cascade='all, delete-orphan')
created_at = Column(DateTime, default=datetime.utcnow)
is_published = Column(Boolean, default=False)
class Comment(Base):
__tablename__ = 'comments'
id = Column(Integer, primary_key=True)
content = Column(Text)
post_id = Column(Integer, ForeignKey('posts.id'))
post = relationship('Post', back_populates='comments')
author_id = Column(Integer, ForeignKey('users.id'))
基本 CRUD
from sqlalchemy.orm import Session
session = Session(bind=engine)
# 创建
user = User(username='zhangsan', email='zhangsan@example.com')
session.add(user)
session.commit()
# 读取
user = session.query(User).filter_by(username='zhangsan').first()
post = session.query(Post).get(1) # 按主键
# 更新
user.email = 'new@example.com'
session.commit()
# 删除
session.delete(user)
session.commit()
高级查询
from sqlalchemy import func, and_, or_, desc
# 条件查询
posts = session.query(Post).filter(
and_(Post.is_published == True, Post.created_at >= '2026-01-01')
).order_by(desc(Post.created_at)).all()
# 模糊搜索
results = session.query(Post).filter(
Post.title.like('%Python%')
).all()
# 聚合统计
stats = session.query(
User.username,
func.count(Post.id).label('post_count')
).join(Post).group_by(User.username).having(
func.count(Post.id) >= 5
).all()
# 子查询
subq = session.query(func.max(Post.created_at)).scalar()
latest_posts = session.query(Post).filter(
Post.created_at == subq
).all()
# 分页
page = 2; page_size = 20
posts = session.query(Post).limit(page_size).offset((page-1)*page_size).all()
关系加载策略
# 懒加载(默认,会 N+1 问题)
posts = session.query(Post).limit(10).all()
for p in posts:
print(p.author.username) # 每次都查数据库!
# 急加载(解决 N+1)
from sqlalchemy.orm import joinedload, selectinload
posts = session.query(Post).options(
joinedload(Post.author)
).limit(10).all()
for p in posts:
print(p.author.username) # JOIN 一次查出
# selectinload(适合一对多)
posts = session.query(Post).options(
selectinload(Post.comments)
).limit(10).all()
批量操作(性能优化)
# 批量插入
session.bulk_insert_mappings(User, [
{'username': f'user{i}', 'email': f'user{i}@example.com'}
for i in range(1000)
])
session.commit()
# 批量更新
session.query(User).filter(User.username.like('test%')).update(
{'is_active': False}, synchronize_session='fetch'
)
session.commit()
# 批量删除
session.query(Post).filter(Post.is_published == False).delete(synchronize_session=False)
session.commit()
事务管理
try:
with session.begin():
user = User(username='newuser', email='new@example.com')
session.add(user)
post = Post(title='First Post', content='...', author_id=user.id)
session.add(post)
# 两者在同一个事务中
except Exception as e:
session.rollback() # 事务回滚
print(f'事务失败: {e}')
性能优化技巧
# 1. 创建索引
from sqlalchemy import Index
Index('idx_post_created', Post.created_at)
Index('idx_post_author_published', 'author_id', 'is_published')
# 2. 查询时只选需要的列
posts = session.query(Post.id, Post.title).filter(...).all()
# 3. 使用 exists 而非 IN
from sqlalchemy import exists
has_posts = session.query(User).filter(
exists().where(Post.author_id == User.id)
).all()
# 4. 连接池配置
engine = create_engine('mysql+pymysql://user:pass@host/db',
pool_size=10, max_overflow=20, pool_recycle=3600)
常见问题
Q1: N+1 查询问题怎么解决?用 joinedload() 或 selectinload() 急加载关联数据。
Q2: session 和 engine 区别?engine 是数据库连接工厂,session 是 ORM 操作的工作单元(Unit of Work)。
Q3: 如何处理数据库连接超时?配置 pool_recycle=3600(1小时刷新连接)和 pool_pre_ping=True(每次使用前检测连接是否有效)。
延伸阅读
- SQLAlchemy 2.0 新特性
- PostgreSQL 高级特性(JSON字段、全文搜索)
- 数据库连接池(SQLAlchemy + Alembic)
---
作者:小马 | 绍大技术网 shaoda.net