1. 为什么需要数据库连接与ORM
在Web开发中,数据持久化是最基础也最关键的环节之一。我见过太多新手开发者把数据直接存在内存或文件里,当服务重启时数据全部丢失的惨剧。MySQL作为最流行的开源关系型数据库,与Flask的搭配堪称经典组合。
十年前我刚接触Flask时,第一个项目就是用的原生SQL语句操作数据库。当时写了大量这样的代码:
python复制conn = pymysql.connect(host='localhost', user='root', password='123456', db='test')
cursor = conn.cursor()
cursor.execute("SELECT * FROM users WHERE id=%s", (user_id,))
这种写法有三个致命问题:SQL注入风险、难以维护的字符串拼接、重复的样板代码。后来ORM的出现彻底改变了这种局面,它用面向对象的方式操作数据库,就像这样:
python复制user = User.query.get(user_id)
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备与基础配置
2.1 MySQL安装与配置避坑指南
虽然网上有很多MySQL安装教程,但根据我多年经验,Windows环境下最容易出问题的环节是服务启动。当你看到"MySQL服务无法启动"的错误时,可以按这个顺序排查:
- 检查my.ini配置文件中的basedir和datadir路径
- 确保没有其他程序占用3306端口
- 查看错误日志(通常在MySQL安装目录的data文件夹下)
重要提示:安装完成后务必设置root密码,并创建专属的应用数据库用户,不要直接用root账号连接应用。
2.2 Flask-SQLAlchemy的选型考量
Python生态中有多个ORM选择,为什么我推荐SQLAlchemy?看这个对比表:
| ORM方案 | 学习曲线 | 性能 | 功能完整性 | Flask集成度 |
|---|---|---|---|---|
| SQLAlchemy | 中 | 高 | 完善 | 高 |
| Peewee | 低 | 中 | 一般 | 低 |
| Django ORM | 低 | 中 | 完善 | 不适用 |
| PonyORM | 高 | 高 | 特殊语法 | 低 |
安装命令很简单:
bash复制pip install flask-sqlalchemy pymysql
但要注意版本兼容性:
- Flask-SQLAlchemy 2.x需要Python 3.6+
- PyMySQL最新版可能不兼容旧版MySQL服务
3. 数据库连接实战
3.1 基础连接配置
在Flask中配置数据库连接,我推荐使用类配置方式:
python复制class Config:
SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://user:password@localhost:3306/db_name'
SQLALCHEMY_TRACK_MODIFICATIONS = False
SQLALCHEMY_ECHO = True # 开发时开启,可以看到生成的SQL
几个容易踩的坑:
- URI中的驱动要写
mysql+pymysql而不仅是mysql - 中文数据需要指定charset:
?charset=utf8mb4 - 生产环境要把ECHO设为False
3.2 连接池优化
在高并发场景下,频繁创建连接会导致性能问题。SQLAlchemy默认使用连接池,但需要合理配置:
python复制SQLALCHEMY_ENGINE_OPTIONS = {
'pool_size': 10,
'max_overflow': 20,
'pool_recycle': 3600, # 1小时回收连接
'pool_pre_ping': True # 检查连接是否存活
}
我曾经在一个日活10万的项目中,因为没设置pool_recycle导致半夜数据库连接全部超时,这个教训让我记忆深刻。
4. 模型定义与CRUD操作
4.1 模型定义最佳实践
定义User模型时,要注意这些细节:
python复制from datetime import datetime
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.now)
def __repr__(self):
return f'<User {self.username}>'
经验之谈:
- 总是显式定义__tablename__,避免自动生成不符合团队规范
- 字符串字段要指定长度,VARCHAR在MySQL中有最大限制
- 时间字段使用datetime而非timestamp,避免时区问题
4.2 完整的CRUD示例
创建(Create)
python复制# 单条创建
new_user = User(username='john', email='john@example.com')
db.session.add(new_user)
db.session.commit()
# 批量创建
users = [User(username=f'user{i}') for i in range(10)]
db.session.add_all(users)
db.session.commit()
查询(Read)
python复制# 基本查询
users = User.query.all()
admin = User.query.filter_by(username='admin').first()
# 复杂查询
from sqlalchemy import or_
result = User.query.filter(
or_(
User.username.like('%john%'),
User.email.endswith('@example.com')
)
).order_by(User.created_at.desc()).limit(10).all()
更新(Update)
python复制user = User.query.get(1)
user.email = 'new@example.com'
db.session.commit() # 只有commit后才会真正更新
删除(Delete)
python复制user = User.query.get(1)
db.session.delete(user)
db.session.commit()
关键点:所有修改操作都需要显式调用commit(),这既是缺点也是优点,可以确保操作的原子性。
5. 高级技巧与性能优化
5.1 事务处理模式
在实际项目中,我推荐使用上下文管理器处理事务:
python复制try:
with db.session.begin():
user1 = User(username='user1')
db.session.add(user1)
user2 = User(username='user2')
db.session.add(user2)
# 这里如果抛出异常,整个事务会回滚
except Exception as e:
current_app.logger.error(f"Transaction failed: {str(e)}")
5.2 查询性能优化
当处理大量数据时,要避免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()
其他优化技巧:
- 使用yield_per分批处理大数据集
- 只查询需要的字段:User.query.with_entities(User.username)
- 合理添加索引(在模型定义中使用__table_args__)
5.3 数据库迁移方案
小型项目可以用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 "create user table"
flask db upgrade
对于大型项目,我建议使用Alembic直接管理迁移脚本,这样可以更精细控制迁移过程。
6. 生产环境注意事项
经过多年实战,我总结了这些血泪教训:
-
连接泄露检测:确保每个请求结束后session被正确关闭,可以使用teardown_request钩子:
python复制@app.teardown_request def shutdown_session(exception=None): db.session.remove() -
慢查询监控:配置SQLALCHEMY_RECORD_QUERIES和app.config['SQLALCHEMY_RECORD_QUERIES'] = True
-
备库读写分离:大型项目可以使用SQLAlchemy的binds功能:
python复制SQLALCHEMY_BINDS = { 'master': 'mysql://...', 'slave1': 'mysql://...' } -
连接失败重试:实现自定义的连接引擎,加入指数退避重试机制
最后提醒:永远不要在代码中硬编码数据库密码!使用环境变量或配置中心管理敏感信息。我在GitHub上见过太多因此导致的数据泄露案例。
