Python中使用Flask与SQLite时的并发写入问题排查

Python中使用Flask与SQLite时的并发写入问题排查

前言

作为一名普通的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框架,并合理配置连接池,以提高系统稳定性和性能。

转载请说明出处内容投诉
CSS教程网 » Python中使用Flask与SQLite时的并发写入问题排查

发表评论

欢迎 访客 发表评论

一个令你着迷的主题!

查看演示 官网购买