我试图理解以下问题的原因让我感到非常头疼。我们正在使用以下库的组合:
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
的文档告诉它不是生产。
答案 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,例如SingletonThreadPool
和NullPool
。
假设您使用的是基于文件的数据库,则应使用NullPool
。这将为您提供良好的读取并发性。写访问并发总是sqlite的问题;如果你需要这个,你可能需要一个diffenet数据库。