1. Flask数据库操作基础概念
Flask作为轻量级Python Web框架,数据库操作是其核心功能之一。与Django自带ORM不同,Flask需要借助扩展来实现数据库交互,这种设计既保持了核心简洁,又提供了灵活的扩展选择。
我刚开始用Flask时,最困惑的就是SQLAlchemy和Flask-SQLAlchemy的区别。简单来说,SQLAlchemy是独立ORM工具,而Flask-SQLAlchemy是其Flask集成版本,提供了更适合Web开发的封装。比如上下文管理、分页支持等Web开发常用功能。
数据库连接的核心流程可以概括为:
- 建立连接配置(URI格式)
- 创建模型类映射数据库表
- 通过会话(Session)执行CRUD操作
- 处理事务提交或回滚
重要提示:生产环境务必使用连接池,直接创建连接会导致性能问题。SQLAlchemy默认就带有连接池机制。
2. 环境配置与初始化
2.1 安装必要依赖
基础环境需要以下包:
pip install flask flask-sqlalchemy pymysql这里我推荐使用pymysql作为MySQL驱动,比默认的mysql-connector更稳定。如果是PostgreSQL,则需要psycopg2。
2.2 配置数据库URI
在config.py中配置典型格式:
class Config: SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://user:password@host:port/dbname' SQLALCHEMY_TRACK_MODIFICATIONS = False # 避免警告 SQLALCHEMY_ECHO = True # 调试时显示SQLURI格式解析:
- mysql+pymysql:// 表示使用MySQL数据库和pymysql驱动
- user:password 是数据库账号密码
- host:port 数据库服务器地址
- /dbname 指定具体数据库
2.3 初始化Flask-SQLAlchemy
在工厂函数中初始化:
from flask_sqlalchemy import SQLAlchemy db = SQLAlchemy() def create_app(): app = Flask(__name__) app.config.from_object(Config) db.init_app(app) return app这种模式支持多应用实例共享同一个db对象,是Flask推荐的做法。
3. 数据模型定义实战
3.1 基础模型示例
典型的用户模型定义:
class User(db.Model): __tablename__ = 'users' # 显式指定表名 id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(80), unique=True, nullable=False) email = db.Column(db.String(120), unique=True) created_at = db.Column(db.DateTime, default=datetime.utcnow) def __repr__(self): return f'<User {self.username}>'字段类型说明:
- Integer: 整数
- String(长度): 字符串
- DateTime: 日期时间
- Boolean: 布尔值
- Float: 浮点数
3.2 高级字段选项
字段常用参数:
name = db.Column(db.String(100), index=True, # 创建索引 nullable=False, # 非空 default='anonymous', # 默认值 server_default='guest') # 数据库端默认值3.3 关系建模
一对多关系示例:
class Post(db.Model): id = db.Column(db.Integer, primary_key=True) author_id = db.Column(db.Integer, db.ForeignKey('users.id')) author = db.relationship('User', backref='posts')多对多关系需要关联表:
tags = db.Table('post_tags', db.Column('post_id', db.Integer, db.ForeignKey('post.id')), db.Column('tag_id', db.Integer, db.ForeignKey('tag.id')) ) class Post(db.Model): tags = db.relationship('Tag', secondary=tags, backref='posts')4. 数据库操作全解析
4.1 创建表
在Flask shell中执行:
flask shell >>> db.create_all()实际项目中更推荐使用迁移工具Alembic:
pip install flask-migrate初始化迁移环境:
from flask_migrate import Migrate migrate = Migrate(app, db)然后执行:
flask db init flask db migrate -m "initial migration" flask db upgrade4.2 CRUD操作
创建记录:
new_user = User(username='john', email='john@example.com') db.session.add(new_user) db.session.commit()查询记录:
# 获取全部 users = User.query.all() # 条件查询 user = User.query.filter_by(username='john').first() # 复杂查询 admins = User.query.filter( User.username.like('%admin%'), User.created_at > datetime(2023,1,1) ).order_by(User.created_at.desc()).limit(10)更新记录:
user = User.query.get(1) user.email = 'new@example.com' db.session.commit()删除记录:
user = User.query.get(1) db.session.delete(user) db.session.commit()4.3 事务处理
事务的基本模式:
try: db.session.begin() # 多个操作 db.session.commit() except: db.session.rollback() raiseFlask-SQLAlchemy会自动在每个请求结束时提交会话,可以通过以下配置关闭:
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = False5. 性能优化实践
5.1 查询优化技巧
避免N+1查询:
# 不好的写法 posts = Post.query.all() for post in posts: print(post.author.username) # 每次循环都查询author # 好的写法 - 使用joinedload from sqlalchemy.orm import joinedload posts = Post.query.options(joinedload(Post.author)).all()只选择需要的字段:
users = db.session.query(User.id, User.username).all()使用原生SQL优化复杂查询:
result = db.session.execute(''' SELECT username, COUNT(posts.id) FROM users LEFT JOIN posts ON users.id = posts.user_id GROUP BY users.id ''')5.2 连接池配置
生产环境推荐配置:
app.config['SQLALCHEMY_ENGINE_OPTIONS'] = { 'pool_size': 20, 'max_overflow': 10, 'pool_timeout': 30, 'pool_recycle': 3600 # 1小时回收连接 }5.3 索引策略
模型定义时添加索引:
class User(db.Model): __table_args__ = ( db.Index('idx_username_email', 'username', 'email'), )6. 常见问题排查
6.1 连接问题
错误:OperationalError: (pymysql.err.OperationalError) (2003, "Can't connect to MySQL server")
解决方案:
- 检查数据库服务是否运行
- 确认防火墙设置
- 验证连接参数是否正确
6.2 编码问题
错误:1366, "Incorrect string value"
需要在URI中指定编码:
SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://...?charset=utf8mb4'6.3 会话管理
错误:SQLAlchemy DetachedInstanceError
这是因为对象与会话分离后访问延迟加载的属性。解决方案:
- 在需要时重新关联会话
- 使用expire_on_commit=False配置
db.session.expire_on_commit = False7. 生产环境最佳实践
7.1 多数据库支持
配置读写分离:
SQLALCHEMY_BINDS = { 'master': 'mysql://master', 'slave': 'mysql://slave' } class User(db.Model): __bind_key__ = 'master' # 默认写操作7.2 监控与日志
配置慢查询日志:
from sqlalchemy import event from sqlalchemy.engine import Engine import time @event.listens_for(Engine, "before_cursor_execute") def before_cursor_execute(conn, cursor, statement, parameters, context, executemany): context._query_start_time = time.time() @event.listens_for(Engine, "after_cursor_execute") def after_cursor_execute(conn, cursor, statement, parameters, context, executemany): duration = time.time() - context._query_start_time if duration > 0.5: # 记录超过500ms的查询 app.logger.warning(f"Slow query: {statement} took {duration:.2f}s")7.3 测试策略
使用内存数据库测试:
class TestConfig(Config): SQLALCHEMY_DATABASE_URI = 'sqlite:///:memory:' TESTING = TruePytest夹具示例:
import pytest @pytest.fixture def db_session(app): with app.app_context(): db.create_all() yield db.session db.session.remove() db.drop_all()