前言
作为一名普通的Python开发者,日常开发中经常会遇到一些看似简单但实际非常棘手的问题。在最近的一个项目中,我遇到了一个关于Flask和SQLite并发写入的bug,这个问题虽然不是特别复杂,但在高并发场景下表现得非常明显,导致数据不一致甚至程序崩溃。今天我想分享一下这个bug的排查过程、解决方法以及避坑经验。
问题现象
我们的项目是一个简单的Web应用,主要功能是记录用户的操作日志。前端通过POST请求发送操作信息到后端,后端使用Flask接收请求,并将日志信息插入到SQLite数据库中。正常情况下,系统运行良好,但当用户量增加或并发请求变多时,会出现以下现象:
- 数据库出现“database is locked”错误;
- 部分日志没有被正确写入;
- 程序有时会抛出异常并退出。
这些现象在测试环境中并不明显,但在生产环境中频繁发生,严重影响了系统的稳定性。
问题分析
初步分析认为,可能是数据库连接管理不当导致的。因为SQLite本身对并发写入的支持较弱,尤其是在多个线程或进程同时进行写操作时容易出现问题。此外,Flask默认的SQLAlchemy配置可能没有正确处理数据库连接池,导致连接数不足或资源未释放。
为了验证这个猜想,我查看了Flask应用的代码结构,发现日志记录部分使用的是直接创建数据库连接的方式,而不是通过SQLAlchemy提供的Session机制。这可能导致每次请求都重新打开一个新的连接,而没有正确关闭,最终造成连接堆积。
排查步骤
步骤一:检查数据库连接方式
首先,我回顾了日志记录模块的代码,发现如下片段:
import sqlite3
def log_operation(user_id, action):
conn = sqlite3.connect('logs.db')
cursor = conn.cursor()
cursor.execute("INSERT INTO logs (user_id, action) VALUES (?, ?)", (user_id, action))
conn.***mit()
conn.close()
这种写法虽然简单,但存在几个潜在问题:
- 每次调用log_operation都会新建一个连接,效率低;
- 如果在某些情况下conn.***mit()或conn.close()没有执行,会导致连接未释放;
- SQLite在高并发下无法处理多个写操作,导致“locked”错误。
步骤二:尝试使用SQLAlchemy管理连接
为了解决这个问题,我决定改用SQLAlchemy来管理数据库连接。首先,安装必要的依赖:
pip install flask-sqlalchemy
然后,在Flask应用初始化时配置数据库:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///logs.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
接下来,定义日志模型:
class Log(db.Model):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer)
action = db.Column(db.String(100))
修改日志记录函数,使用SQLAlchemy的session:
from flask import g
def get_db():
if 'db' not in g:
g.db = SQLAlchemy().create_engine('sqlite:///logs.db')
return g.db
def log_operation(user_id, action):
db = get_db()
db.execute("INSERT INTO logs (user_id, action) VALUES (?, ?)", (user_id, action))
db.***mit()
这样,数据库连接由SQLAlchemy统一管理,避免了频繁打开和关闭连接的问题。
步骤三:测试与优化
经过上述修改后,我在本地模拟了高并发场景,使用locust工具进行压力测试。测试结果显示,数据库“locked”错误大大减少,日志写入成功率显著提升。
然而,我发现即使使用SQLAlchemy,SQLite仍然无法很好地支持高并发写入。因此,我考虑将数据库切换为PostgreSQL,以获得更好的并发性能。
步骤四:更换数据库为PostgreSQL
首先,安装PostgreSQL驱动:
pip install psycopg2-binary
然后修改Flask配置:
app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://user:password@localhost/dbname'
重新运行应用后,所有并发写入问题消失,系统稳定性得到极大提升。
总结
这次经历让我深刻认识到,在使用Flask与SQLite进行高并发写入时,需要特别注意数据库连接管理和并发控制。虽然SQLite适合轻量级应用,但在高并发场景下,建议使用更强大的数据库如PostgreSQL或MySQL。
此外,使用SQLAlchemy可以有效提高代码可维护性,并减少因手动管理连接而导致的错误。对于类似问题,建议优先使用成熟的ORM框架,并合理配置连接池,以提高系统稳定性和性能。