【问题标题】:SQLAlchemy + pyTelegramBotAPI: SQLite objects created in a thread can only be used in that same threadSQLAlchemy + pyTelegramBotAPI:在一个线程中创建的 SQLite 对象只能在同一个线程中使用
【发布时间】:2017-03-01 16:35:34
【问题描述】:

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

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 的文档表明它不是生产盛行。

【问题讨论】:

  • 当您说NullThreadPool 时,您是指NullPool 吗?为什么需要使用QueuePool
  • 来自 SQLAlchemy 文档:“当指定基于文件的数据库时,方言将使用 NullPool 作为连接源。此池将关闭并丢弃立即返回池的连接。SQLite 文件-基于连接的开销极低,因此不需要池化。该方案还可以防止连接在不同的线程中再次使用,并且最适合 SQLite 的粗粒度文件锁定。"
  • 正如 xli 所说,您为什么要尝试使用QueuePooldocs 很清楚,你可以使用NullPoolSingletonThreadPoolStaticPool
  • @xli,是的,我的意思是NullPool ;) 感谢您的更正。使用QueuePool 的想法是让多个(读取)线程同时可用。为什么会出错? @xli,@Peter Brittain,SqlAlchemy 的默认实现使用NullPool,但我在使用它时遇到了异常(如上所述)。我不知道我为什么要得到它们,因为通过使用@contextmanager 应该保证我每个线程打开一个连接...
  • 我收到了您在多个线程使用QueuePool 执行数据库操作时显示的错误,但它在NullPool 中消失了。您是否可以在 with 块中有多个线程,或者您能描述一下您在那里做什么?

标签: python multithreading sqlite sqlalchemy py-telegram-bot-api


【解决方案1】:

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

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

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

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

【讨论】:

    【解决方案2】:

    可能值得尝试的东西:使用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()

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

    【讨论】:

      猜你喜欢
      • 2018-06-21
      • 2020-09-12
      • 2021-09-20
      • 2016-03-04
      • 2019-10-21
      • 2021-01-16
      • 2019-10-30
      • 1970-01-01
      • 2021-06-10
      相关资源
      最近更新 更多