SQLAlchemy + pyTelegramBotAPI:在一个线程中创建的 SQLite 对象只能在同一个线程中使用

SQLAlchemy + pyTelegramBotAPI: SQLite objects created in a thread can only be used in that same thread

我真的很头疼试图理解以下问题的原因。我们正在使用以下库的组合:

SQLAlchemy 最初使用 NullPool,现在配置为使用 QueuePool。我还使用以下习惯用法为每个线程启动一个新的数据库会话(根据我的理解)

Session = sessionmaker(bind=create_engine(classes.db_url, poolclass=QueuePool))

@contextmanager
def session_scope():
   session = Session()
   try:
      yield session
      session.commit()
   except:
      session.rollback()
      raise
   finally:
      session.close()

@bot.message_handler(content_types=['document'])
def method_handler:
   with session_scope() as session:
      do_database_stuff_here(session)

尽管如此,我仍然遇到这个烦人的异常:(sqlite3.ProgrammingError) SQLite objects created in a thread can only be used in that same thread

有什么想法吗? ;) 特别是,我不明白另一个步骤是如何进入数据库操作之间的某个地方的……这可能是讨厌的异常的原因

更新 1:如果我将 poolclass 更改为 SingletonThreadPool,那么似乎不会再出现错误。但是,SQLAlchemy 的文档表明它不是生产环境。

如您在 the source 中所见,如果跨任何线程重用连接对象,sqlite 将在 pysqlite_check_thread 内引发此异常。

通过使用 QueuePool,您告诉 SQLAchemy 跨多个线程重用连接是安全的。因此,它只会从池中为任何会话选择一个连接,无论它在哪个线程上。这就是你遇到错误的原因。第一次创建和使用连接时,您会没事的;但是下一次使用可能会在不同的线程上,因此检查失败。

这就是为什么 SQLAlchemy mandates the use of other poolsSingletonThreadPoolNullPool.

假设您使用的是基于文件的数据库,您应该使用 NullPool。这将为您提供良好的读取并发性。写入访问并发性始终是 sqlite 的一个问题;如果你需要这个,你可能需要一个 diffenet 数据库。

可能值得尝试的东西:使用 scoped_session 而不是你的 contextmanagerscoped_session 当从不同的线程访问时隐式创建一个线程本地会话。一定要使用 NullPool.

from sqlalchemy.orm import scoped_session
sessionmaker(bind=create_engine(classes.db_url, poolclass=NullPool))
session = scoped_session()

请注意,您可以像使用常规 session 一样直接使用此作用域 session,即使它在使用时实际上是在幕后创建线程本地会话。

对于 scoped_session,应该在完成后(即在每个 method_handler 之后)调用 session.remove(),并根据需要明确调用 session.commit()

理论上,您的上下文管理器应该为每个线程提供自己的会话,但是,由于缺乏更好的解释,我想知道是否有多个线程在上下文中访问该会话。