我们使用CherryPy和SQLAlchemy构建我们的Web应用程序,一切都很好,直到我们测试了2个并发用户 - 然后事情开始出错了!对于一个网络应用程序来说不是很好,所以如果有人能对此有所启发,我会非常感激。
TL; DR
当两个用户同时使用我们的网站(但访问不同的数据库)时,我们会在10%的时间内收到以下错误:
ProgrammingError: (ProgrammingError) (1146, "Table 'test_one.other_child_entity' doesn't exist")
该表不存在于该数据库中,因此错误有意义,但问题是SQLAlchemy不应该在该数据库中查找该表。
我在此处的示例https://gist.github.com/1729817
中重现了错误解释
我们正在开发一个非常动态的应用程序,它基于http://www.sqlalchemy.org/trac/wiki/UsageRecipes/EntityName
中的entity_name
模式
我们已经发展了这个想法,以便根据您登录的用户将实体存储在不同的数据库中。这是因为系统中的每个用户都有自己的数据库,可以创建自己的实体(表)。为此,我们为每个数据库扩展一个基本实体,然后为他们在数据库中创建的每个附加实体扩展该新实体。
当应用程序启动时,我们创建一个包含所有这些数据库的引擎,元数据,类和表的字典,并反映所有元数据。当用户登录时,他们可以访问一个。
当两个用户同时访问该站点时出现问题,SQLAlchemy最终会在错误的数据库中查找表。我想这与线程有关,但据我所知,当涉及到会话(CP和SQLA),引擎,元数据,表和映射器时,我们遵循所有规则。
如果有人能给我的例子(https://gist.github.com/1729817)快速浏览一下,并指出任何明显的问题,那就太棒了。
更新
我可以通过更改代码来解决问题,使用我自己的自定义会话路由器,如下所示:
# Thank you zzzeek (http://techspot.zzzeek.org/2012/01/11/django-style-database-routers-in-sqlalchemy/)
class RoutingSession(Session):
def get_bind(self, mapper = None, clause = None):
return databases[cherrypy.session.get('database')]['engine']
然后:
Session = scoped_session(sessionmaker(autoflush = True, autocommit = False, class_ = RoutingSession))
所以只需对其进行硬编码即可返回链接到会话中设置的数据库的引擎。这是个好消息,但现在我想知道为什么我的原始代码不起作用。要么我做错了,要么以下代码不完全安全:
# Before each request (but after the session tool)
def before_request_body():
if cherrypy.session.get('logged_in', None) is True:
# Configure the DB session for this thread to point to the correct DB
Session.configure(bind = databases[cherrypy.session.get('database')]['engine'])
我想在这里发生的绑定被另一个线程中的用户覆盖了,这很奇怪,因为我认为scoped_session
是关于线程安全的?