yield_per过程中发生sqlalchemy游标错误



我得到以下错误:

Traceback (most recent call last):
    main()
    for item in session.query(Item).yield_per(10):
    fetch = cursor.fetchmany(self._yield_per)
    self.cursor, self.context)
    l = self.process_rows(self._fetchmany_impl(size))
    row = self._fetchone_impl()
    self.__buffer_rows()
    self.__rowbuffer = collections.deque(self.cursor.fetchmany(size))
sqlalchemy.exc.ProgrammingError: (ProgrammingError) named cursor isn't valid anymore None None

我怀疑调用session.commit()正在干扰.yield_per

sessionmaker_ = sessionmaker(autocommit=False, autoflush=False, bind=engine)
session = scoped_session(sessionmaker_)
def foo(item):
  # DO something to the item 
  session.add(item)
  session.commit()
def main():
  for item in session.query(Item).yield_per(5):
    foo(item)

知道吗?

如果您还没有从DBAPI游标中提取所有行,那么在该游标的连接上调用commit()通常是个坏主意。在这种情况下,psycopg2(我猜这是您使用的DBAPI)无法在事务上维护命名游标的状态(当您需要服务器缓冲行时,它会使用该游标)。

这里有一件事你绝对应该改变,那就是你的承诺频率。理想情况下,在整个操作完成之前,您不会提交任何内容。Session会根据需要自动刷新数据(好吧,如果你打开了autoflush,我建议你这样做),或者你可以调用flush()来强制执行,但这与实际提交事务无关。所有这些对commit()的调用都会使操作的效率大大降低,当然,这也会妨碍其他结果集的游标。如果你只在循环的末尾放一个commit(),那么你就可以同时解决这两个问题。

如果您仍然需要在整个操作完成之前提交,或者即使不需要,我更喜欢分块工作,而不是使用yield_per(),这是非常脆弱的。上的配方http://www.sqlalchemy.org/trac/wiki/UsageRecipes/WindowedRangeQuery展示了一种方法。DBAPI总体上不太适合处理超大的结果集,尽管psycopg2给了我们更多的回旋余地。

以上问题可以通过一个会话解决

sessionmaker_ = sessionmaker(autocommit=False, autoflush=False, bind=engine)
session = scoped_session(sessionmaker_)
cool_session = scoped_session(sessionmaker_)
def foo(item):
   # DO something to the item 
   session.add(item)
   session.commit()
def main():
    for item in cool_session.query(Item).yield_per(5):
    item = session.merge(item, load=False)
    foo(item)

当它进入sqlalchemy版本时,另一个选项是使用光标选项"WITH HOLD":https://bitbucket.org/zzzeek/sqlalchemy/issues/3667/support-postgresqls-with-hold-cursor

请注意Scott提到的关于Postgres在第一次提交时具体化游标的警告。

最新更新