我的项目后端是python-flask框架,使用了flask-restx来开发RESTful API。
前几天遇到一个问题:想要在restx的某个命名空间中调用多线程来批量提交数据库。但是报错了,显示需要使用 with app.app_context() 来设置一个上下文。
但是我的项目采用工程模式创建flask app实例。并且在使用了 flask-restx 命名空间的情况下,如果模块需要调用with app.app_context()是无法实现的。会触发循环导入。
推荐使用SQLAlchemy的 scoped_session 来创建一个线程安全的会话:
from sqlalchemy import create_engine
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.orm import DeclarativeBase, sessionmaker, scoped_session
# 定义基类
class Base(DeclarativeBase):
pass
# 创建数据库引擎
engine = create_engine() # 数据库的连接配置
# 创建线程安全的会话
db_session = scoped_session(sessionmaker(bind=engine))
# 初始化 SQLAlchemy
db = SQLAlchemy(model_class=Base)
def init_db(app):
#app.config... # 数据库配置
db.init_app(app)
db.session = db_session # 绑定session到app
这样在blueprint或者restx namespace中,将之前的db操作换成db_session即可(以下代码做了脱敏):
测试了以下10w条数据,一次性插入的时间大约为90秒,分10个线程各自插入1w条,下降到22秒左右,整体耗时减少了75%。
from flask_restx import Resource, Namespace
from db import db_session
import time,
from threading import Thread
api = Namespace()
@api.route('/')
class AddToDB(Resource):
def get(self):
start = int(time.time())
result = {}
insert_num = 100000
threads = []
for i in range(10):
thread = Thread(target=lambda: self.insert())
threads.append(thread)
thread.start()
for thread in threads:
thread.join()
print(f'插入{insert_num}一共用时: {int(time.time()) - start}')
return result
def insert(self):
session = db_session()
try:
for j in range(10000):
new_item = item()
session.add(new_item)
session.commit()
except Exception as e:
session.rollback()
print(f'数据库操作出错: {e}')
return
db_session()比db.session有以下区别:
- 它是线程安全的,并且可以在多线程环境中使用。
- 不自动绑定到 Flask 应用上下文,因此可以在请求之外的代码中使用,例如在多线程任务。
- 需要手动调用 commit() 或 rollback() 来提交或回滚事务。
- 自动管理会话的生命周期,不需要在每个线程结束后手动关闭会话。