SQLAlchemy + pyTelegramBotAPI:在一个线程中创建的 SQLite 对象只能在同一个线程中使用
SQLAlchemy + pyTelegramBotAPI: SQLite objects created in a thread can only be used in that same thread
我真的很头疼试图理解以下问题的原因。我们正在使用以下库的组合:
- pyTelegramBotAPI以多线程方式处理请求
- SQLAlchemy
- sqlite
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 pools 如 SingletonThreadPool
和 NullPool
.
假设您使用的是基于文件的数据库,您应该使用 NullPool
。这将为您提供良好的读取并发性。写入访问并发性始终是 sqlite 的一个问题;如果你需要这个,你可能需要一个 diffenet 数据库。
可能值得尝试的东西:使用 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
,应该在完成后(即在每个 method_handler
之后)调用 session.remove()
,并根据需要明确调用 session.commit()
。
理论上,您的上下文管理器应该为每个线程提供自己的会话,但是,由于缺乏更好的解释,我想知道是否有多个线程在上下文中访问该会话。
我真的很头疼试图理解以下问题的原因。我们正在使用以下库的组合:
- pyTelegramBotAPI以多线程方式处理请求
- SQLAlchemy
- sqlite
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 pools 如 SingletonThreadPool
和 NullPool
.
假设您使用的是基于文件的数据库,您应该使用 NullPool
。这将为您提供良好的读取并发性。写入访问并发性始终是 sqlite 的一个问题;如果你需要这个,你可能需要一个 diffenet 数据库。
可能值得尝试的东西:使用 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
,应该在完成后(即在每个 method_handler
之后)调用 session.remove()
,并根据需要明确调用 session.commit()
。
理论上,您的上下文管理器应该为每个线程提供自己的会话,但是,由于缺乏更好的解释,我想知道是否有多个线程在上下文中访问该会话。