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

时间:2016-10-19 21:04:40

标签: python multithreading sqlite sqlalchemy telegram-bot

我试图理解以下问题的原因让我感到非常头疼。我们正在使用以下库的组合:

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

有什么想法吗? ;)特别是,我不知道如何有可能让另一个步骤在db操作之间的某处...这可能是令人讨厌的异常的原因

更新1 :如果我将poolclass更改为SingletonThreadPool,则似乎不会再出现错误。但是,SQLAlchemy的文档告诉它不是生产。

2 个答案:

答案 0 :(得分:4)

可能值得尝试的事情:使用scoped_session而不是contextmanager; scoped_session在从其他线程访问时隐式创建线程本地会话。请务必使用NullPool

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

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

对于scoped_session,请在完成后(即每次session.remove()之后)致电method_handler,并根据需要明确致电session.commit()

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

答案 1 :(得分:3)

正如您在the source中看到的那样,如果在任何线程中重用连接对象,sqlite将在pysqlite_check_thread内引发此异常。

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

这就是为什么SQLAlchemy mandates the use of other pools,例如SingletonThreadPoolNullPool

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