1. Flask与MySQL数据库连接基础
Flask作为轻量级Python Web框架,与MySQL数据库的搭配是中小型项目的经典组合。我们先从最基础的连接配置开始讲起。
1.1 环境准备与依赖安装
在开始之前,确保你已经安装了以下组件:
- Python 3.6+
- MySQL 5.7+ 或 MariaDB 10.2+
- Flask 2.0+
需要安装的核心Python包:
bash复制pip install flask pymysql flask-sqlalchemy
这里我特别推荐使用Flask-SQLAlchemy而不是直接使用PyMySQL,原因有三:
- ORM层提供了更高级的抽象,减少SQL注入风险
- 数据库迁移支持更完善
- 查询构建更符合Python风格
1.2 基础连接配置
在Flask应用中配置MySQL连接的标准方式:
python复制from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://username:password@localhost/dbname'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
几个关键配置项说明:
mysql+pymysql:指定使用MySQL数据库和PyMySQL驱动username:password:你的MySQL凭据localhost:数据库服务器地址dbname:要连接的数据库名称
重要提示:永远不要在代码中直接硬编码数据库密码!应该使用环境变量或配置文件管理敏感信息。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. ORM模型定义与表结构映射
2.1 定义第一个模型
假设我们要创建一个用户管理系统,首先定义User模型:
python复制class User(db.Model):
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, nullable=False)
created_at = db.Column(db.DateTime, default=db.func.current_timestamp())
def __repr__(self):
return f'<User {self.username}>'
字段类型说明:
db.Integer:整型,对应MySQL的INTdb.String(length):可变长度字符串,对应VARCHARdb.DateTime:日期时间类型primary_key:设置为主键unique:唯一约束nullable:是否允许NULL值
2.2 数据库迁移与表创建
使用Flask-Migrate进行数据库迁移管理:
bash复制pip install flask-migrate
初始化迁移环境:
python复制from flask_migrate import Migrate
migrate = Migrate(app, db)
然后在命令行执行:
bash复制flask db init
flask db migrate -m "initial migration"
flask db upgrade
这样就会在MySQL中创建对应的表结构。我强烈建议始终使用迁移工具而不是直接执行CREATE TABLE,因为:
- 可以跟踪结构变更历史
- 方便团队协作
- 支持回滚操作
3. 完整的CRUD操作实现
3.1 创建数据(Create)
添加新用户的几种方式:
python复制# 方法1:直接实例化
new_user = User(username='john', email='john@example.com')
db.session.add(new_user)
db.session.commit()
# 方法2:使用字典解包
user_data = {'username': 'alice', 'email': 'alice@example.com'}
new_user = User(**user_data)
db.session.add(new_user)
db.session.commit()
# 批量添加
users = [
User(username='user1', email='user1@test.com'),
User(username='user2', email='user2@test.com')
]
db.session.add_all(users)
db.session.commit()
常见陷阱:忘记调用commit()!所有修改必须显式提交才会生效。
3.2 查询数据(Read)
基础查询方法:
python复制# 获取所有用户
users = User.query.all()
# 获取单个用户
user = User.query.get(1) # 通过主键查询
# 条件查询
admin_users = User.query.filter_by(is_admin=True).all()
# 复杂条件查询
recent_users = User.query.filter(
User.created_at > '2023-01-01'
).order_by(
User.created_at.desc()
).limit(10).all()
查询方法链是SQLAlchemy的强大特性,可以组合多个操作:
python复制query = User.query
query = query.filter(User.email.endswith('@example.com'))
query = query.order_by(User.username)
query = query.limit(5)
results = query.all()
3.3 更新数据(Update)
更新记录的推荐方式:
python复制user = User.query.get(1)
user.email = 'new_email@example.com'
db.session.commit()
批量更新:
python复制User.query.filter_by(is_admin=False).update({'status': 'inactive'})
db.session.commit()
性能提示:批量操作时,考虑使用bulk_update_mappings方法提高效率。
3.4 删除数据(Delete)
删除单条记录:
python复制user = User.query.get(1)
db.session.delete(user)
db.session.commit()
批量删除:
python复制User.query.filter_by(is_active=False).delete()
db.session.commit()
4. 高级查询与性能优化
4.1 关联查询与JOIN操作
假设我们扩展模型,添加文章和评论:
python复制class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(120))
content = db.Column(db.Text)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
user = db.relationship('User', backref='posts')
class Comment(db.Model):
id = db.Column(db.Integer, primary_key=True)
content = db.Column(db.Text)
post_id = db.Column(db.Integer, db.ForeignKey('post.id'))
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
post = db.relationship('Post', backref='comments')
user = db.relationship('User', backref='comments')
关联查询示例:
python复制# 获取用户的所有文章及其评论
user = User.query.get(1)
for post in user.posts:
print(post.title)
for comment in post.comments:
print(f"- {comment.content}")
# 使用JOIN高效查询
posts_with_comments = db.session.query(Post, Comment).join(
Comment, Post.id == Comment.post_id
).filter(
Post.user_id == 1
).all()
4.2 分页查询实现
对于大量数据,必须实现分页:
python复制page = request.args.get('page', 1, type=int)
per_page = 20
users = User.query.order_by(
User.created_at.desc()
).paginate(
page=page,
per_page=per_page,
error_out=False
)
# 模板中使用
# {% for user in users.items %}
# ...
# {% endfor %}
4.3 性能优化技巧
- 避免N+1查询问题:
python复制# 不好的方式(会产生N+1查询)
users = User.query.all()
for user in users:
print(user.posts) # 每次迭代都会查询数据库
# 好的方式(使用joinedload)
from sqlalchemy.orm import joinedload
users = User.query.options(joinedload(User.posts)).all()
- 只选择需要的字段:
python复制# 不推荐
users = User.query.all() # 选择所有字段
# 推荐
users = User.query.with_entities(User.id, User.username).all()
- 使用索引提示:
python复制from sqlalchemy import text
User.query.filter(text("username = :name")).params(name='john').all()
5. 实战中的常见问题与解决方案
5.1 连接池配置
默认情况下,SQLAlchemy会维护一个连接池。生产环境中应该调整配置:
python复制app.config['SQLALCHEMY_POOL_SIZE'] = 5
app.config['SQLALCHEMY_MAX_OVERFLOW'] = 10
app.config['SQLALCHEMY_POOL_RECYCLE'] = 1800 # 30分钟后回收连接
app.config['SQLALCHEMY_POOL_TIMEOUT'] = 30
5.2 事务处理模式
正确的错误处理和事务管理:
python复制try:
user = User(username='test', email='test@example.com')
db.session.add(user)
db.session.commit()
except Exception as e:
db.session.rollback()
current_app.logger.error(f"Failed to create user: {str(e)}")
raise
finally:
db.session.close()
5.3 数据库迁移问题解决
常见迁移问题及解决方法:
- 迁移冲突:
bash复制flask db stamp head # 标记当前数据库状态
flask db merge -m "merge branches" # 合并冲突的迁移
- 回滚迁移:
bash复制flask db downgrade # 回退一步
flask db downgrade base # 回退到初始状态
- 手动干预迁移:
有时需要手动编辑迁移文件,特别是当自动检测失败时。
5.4 生产环境部署建议
- 使用连接池:考虑使用更强大的连接池如SQLAlchemy-PoolBoy
- 监控慢查询:配置SQLALCHEMY_RECORD_QUERIES=True并记录慢查询
- 定期维护:优化表、分析查询执行计划
- 备份策略:设置自动备份机制
6. 扩展功能实现
6.1 多数据库支持
配置多个数据库连接:
python复制app.config['SQLALCHEMY_BINDS'] = {
'users': 'mysql+pymysql://user:pass@localhost/users_db',
'products': 'mysql+pymysql://user:pass@localhost/products_db'
}
class User(db.Model):
__bind_key__ = 'users'
# ...
class Product(db.Model):
__bind_key__ = 'products'
# ...
6.2 读写分离实现
通过自定义路由实现读写分离:
python复制class ReadWriteRouter:
def __init__(self, read_uri, write_uri):
self.read_uri = read_uri
self.write_uri = write_uri
def get_uri(self, is_write=False):
return self.write_uri if is_write else self.read_uri
router = ReadWriteRouter(
read_uri='mysql+pymysql://readuser:pass@read-replica/db',
write_uri='mysql+pymysql://writeuser:pass@master/db'
)
@app.before_request
def before_request():
if request.method in ('POST', 'PUT', 'DELETE', 'PATCH'):
g.is_write_operation = True
else:
g.is_write_operation = False
def get_engine():
return create_engine(router.get_uri(getattr(g, 'is_write_operation', False)))
6.3 使用Alembic进行高级迁移
对于复杂项目,直接使用Alembic可能更灵活:
python复制# alembic/env.py
from models import db
target_metadata = db.metadata
# 然后可以使用更精细的迁移控制
def upgrade():
op.create_table(
'new_table',
sa.Column('id', sa.Integer(), nullable=False),
# ...
)
op.alter_column('existing_table', 'old_column', new_column_name='new_column')
7. 测试策略与Mock数据库
7.1 单元测试配置
使用SQLite内存数据库进行测试:
python复制import unittest
from app import create_app, db
class TestCase(unittest.TestCase):
def setUp(self):
self.app = create_app('testing')
self.app_context = self.app.app_context()
self.app_context.push()
db.create_all()
def tearDown(self):
db.session.remove()
db.drop_all()
self.app_context.pop()
7.2 使用Factory Boy创建测试数据
python复制import factory
from models import User
class UserFactory(factory.alchemy.SQLAlchemyModelFactory):
class Meta:
model = User
sqlalchemy_session = db.session
username = factory.Faker('user_name')
email = factory.Faker('email')
7.3 性能测试建议
使用locust进行数据库性能测试:
python复制from locust import HttpUser, task, between
class WebsiteUser(HttpUser):
wait_time = between(1, 5)
@task
def create_user(self):
self.client.post("/users", json={
"username": "testuser",
"email": "test@example.com"
})
@task
def get_users(self):
self.client.get("/users")
8. 安全最佳实践
8.1 防止SQL注入
虽然ORM已经提供了基本防护,但仍需注意:
- 永远不要直接拼接SQL
- 使用参数化查询
- 验证所有用户输入
8.2 敏感数据处理
- 加密存储密码(使用如bcrypt)
- 日志中过滤敏感信息
- 实现数据访问控制
8.3 审计日志
记录关键数据变更:
python复制from sqlalchemy import event
@event.listens_for(User, 'after_update')
def receive_after_update(mapper, connection, target):
changes = {}
for attr in inspect(target).attrs:
hist = attr.load_history()
if hist.has_changes():
changes[attr.key] = {
'old': str(hist.deleted[0]) if hist.deleted else None,
'new': str(hist.added[0]) if hist.added else None
}
if changes:
audit = AuditLog(
model='User',
record_id=target.id,
action='update',
changes=json.dumps(changes),
user_id=current_user.id if current_user else None
)
db.session.add(audit)
在实际项目中,我发现ORM的使用虽然方便,但也容易导致开发者忽略底层数据库知识。建议每个Flask开发者都应该:
- 定期检查生成的SQL语句
- 了解MySQL的索引原理
- 掌握EXPLAIN分析查询性能
- 根据业务特点选择合适的隔离级别
最后一个小技巧:在开发环境设置SQLALCHEMY_ECHO=True可以打印所有SQL语句,对调试非常有帮助。
