SQLAlchemy的Db会话在并行调用时出错

0 投票
1 回答
30 浏览
提问于 2025-04-14 16:05

我创建了一个数据库会话,代码如下:

db_engine = create_engine(DB_URL, pool_pre_ping=True, echo=False, pool_size=POOL_SIZE, max_overflow=MAX_OVERFLOW)
Base.metadata.create_all(db_engine)
db_session = sessionmaker(bind=db_engine)()

我在一个Flask路由中使用了这个数据库会话,代码是:

from db_settings import db_session

@application.route('/'):
def my_home():
    try:
        my_obj = MyObj("dummy_fname", "dummy_lname")
        db_session.merge(my_obj)
        db_session.commit()
    except exc.IntegrityError:
        db_session.rollback()
    finally:
        db_session.close()

我原本以为每次访问这个路由时,数据库会话都是独立的。我尝试连续访问这个路由5次,结果成功在数据库中创建了5条记录,没有任何问题。但是,当我尝试使用线程同时访问这个路由时,只创建了一条记录,其他四次调用都出错了。有人能帮我理解一下这个情况吗?为什么会这样?是因为db_session.close()影响了其他API调用吗?

import requests
import threading

no_parallel_hits = no_of_sequential_hits = 5


def send_request():
    url = 'http://127.0.0.1:5000/'
    response = requests.get(url)
    print(response.text)


def main_parallel():
    threads = []
    for _ in range(no_parallel_hits):
        thread = threading.Thread(target=send_request)
        threads.append(thread)
        thread.start()

    for thread in threads:
        thread.join()


def main_sequential():
    for _ in range(no_of_sequential_hits):
        send_request()

if __name__ == "__main__":
    main_parallel()
    # main_sequential()

这是我尝试的结果。main_sequential()调用正常,而main_parallel()却没有按预期工作。

1 个回答

0

在这里,你创建了一个会话生成器,然后生成了一个会话实例。这个实例不应该被同时使用,也不应该放在全局范围内。

# Usually NOT what you want.
db_session = sessionmaker(bind=db_engine)()

更好的做法是这样:

db_session_maker = sessionmaker(bind=db_engine)

然后可以这样创建一个会话:

from db_settings import db_session_maker

@application.route('/'):
def my_home():
    db_session = db_session_maker()
    try:
        my_obj = MyObj("dummy_fname", "dummy_lname")
        db_session.add(my_obj)
        db_session.commit()
    except exc.IntegrityError:
        db_session.rollback()
    finally:
        db_session.close()

更理想的方式是使用某种 Flask 会话管理器,这样可以帮你处理会话的创建和关闭。SQLAlchemy 提到要把会话管理和你的应用代码分开。session-faq-whentocreate

另外,你应该使用 db_session.add 来添加新对象,而不是 db_session.merge,后者是用来处理不同情况的。

撰写回答