应使用 sqlalchemy 而非原生 pymysql 或 mysql-connector,因其自动管理连接池、事务和线程安全;连接字符串须为 mysql+pymysql://...?charset=utf8mb4,sqlalchemy_database_uri 需在 db = sqlalchemy(app) 前配置,且必须关闭 sqlalchemy_track_modifications 以避免内存泄漏。

用 SQLAlchemy 而不是原生 mysql-connector 或 pymysql
Flask 本身不内置数据库驱动,直接写 pymysql.connect() 或手动管理连接会很快失控——连接没关、事务没提交、多请求下复用错对象。生产环境几乎都用 SQLAlchemy(ORM + 连接池),它能自动处理连接复用、超时回收、线程安全等底层问题。
安装:
pip install flask-sqlalchemy pymysql
SQLAlchemy 默认用 pymysql 作 MySQL 底层驱动(比 mysql-connector-python 更轻、兼容性更好,尤其在 Python 3.9+)。
- 连接字符串格式必须是:
mysql+pymysql://<username>:<password>@<host>:<port>/<database>?charset=utf8mb4</database></port></host></password></username> -
charset=utf8mb4是硬性要求,否则存 emoji 或四字节 UTF-8 字符会报Incorrect string value - 别漏掉
mysql+pymysql://前缀,写成mysql://会默认找已卸载的mysqldb,报ModuleNotFoundError: No module named 'MySQLdb'
SQLALCHEMY_DATABASE_URI 必须设为全局配置,且不能运行时改
Flask-SQLAlchemy 在 app = Flask(__name__) 后就立刻读取配置,一旦 db = SQLAlchemy(app) 执行完,再改 app.config['SQLALCHEMY_DATABASE_URI'] 就无效了——新连接还是用旧地址,但模型查询可能报 AttributeError: 'NoneType' object has no attribute 'execute'。
正确写法:
app = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://root:123456@localhost:3306/myapp?charset=utf8mb4' app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False # 必关,否则内存泄漏 db = SQLAlchemy(app)
-
SQLALCHEMY_TRACK_MODIFICATIONS = False不是可选项,是必须项;设为True会让 SQLAlchemy 监听每个对象变更,吃光内存 - 密码含特殊字符(如
@、/)必须用urllib.parse.quote_plus()编码,否则解析 URL 失败,报Invalid URL - 开发时可用
sqlite:///dev.db快速验证逻辑,但上线前务必换成 MySQL 地址——SQLite 和 MySQL 的 SQL 行为差异很大(比如GROUP BY严格性、datetime自动填充)
第一次 db.create_all() 失败?先确认数据库存在,且用户有权限
db.create_all() 不会自动创建 MySQL 数据库(schema),只建表。如果 myapp 库不存在,会报错:sqlalchemy.exc.OperationalError: (pymysql.err.OperationalError) (1049, "Unknown database 'myapp'")。
解决步骤:
- 手动登录 MySQL:
mysql -u root -p,然后执行:CREATE DATABASE IF NOT EXISTS myapp CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; - 确保连接用户(如
root)对这个库有SELECT, INSERT, UPDATE, DELETE, CREATE, INDEX权限;仅SELECT权限会导致create_all()静默跳过建表 - 建表后检查字符集:
SHOW CREATE TABLE users;,确认每张表末尾是ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci,否则中文可能乱码
查数据报 ObjectDeletedError 或 DetachedInstanceError?说明 session 生命周期搞错了
常见于视图函数里查出对象,返回前没 db.session.expunge_all(),又在模板或 JSON 序列化时访问了延迟加载字段(如 user.posts)。这时 ORM 已关闭 session,但对象还带着“懒加载钩子”,一触发就崩。
最稳妥的做法是:查完立刻转成 dict 或用 marshal_with 显式控制输出字段,别把 ORM 实例直接扔给前端。
# ❌ 危险
user = User.query.get(1)
return jsonify(user) # user.posts 触发懒加载 → DetachedInstanceError
<h1>✅ 安全</h1><p>user = User.query.options(db.joinedload(User.posts)).get(1)
return jsonify({
'id': user.id,
'name': user.name,
'posts': [p.title for p in user.posts]
})</p>
- 不要依赖
db.session.remove()手动清理——Flask-SQLAlchemy 在每次请求结束时自动调用它 - 异步任务(如 Celery)里用
db,必须自己db.session.remove()结束后清理,否则连接泄漏 - 调试时加
app.config['SQLALCHEMY_ECHO'] = True可打印所有 SQL,但上线必须关,否则 I/O 拖慢整个服务
实际部署时,MySQL 连接池参数(如 pool_size、max_overflow)和应用并发量强相关,盲目调大反而导致数据库连接数打满。先用默认值跑压测,再根据 show status like 'Threads_connected'; 和错误日志调整。











