更好地理解SQLalchemy的`yield_per()`问题

时间:2015-07-13 15:54:11

标签: python mysql orm sqlalchemy

引用SQLalchemy documentation

  

Query.yield_per()方法与大多数热切加载方案不兼容,包括subqueryload和joinload with collections。

     

警告

     

谨慎使用此方法;如果同一个实例存在于多个行中,则最终用户对属性的更改将被覆盖。

     

特别是,通常不可能将此设置与急切加载的集合(即任何lazy ='joined'或'子查询')一起使用,因为在后续结果批处理中遇到这些集合时将清除新负载。在“子查询”加载的情况下,获取所有行的完整结果,这通常会违背yield_per()的目的。

     

另请注意,虽然yield_per()会将stream_results执行选项设置为True,但目前只能通过psycopg2 dialect来理解这一点,它将使用服务器端游标流式传输结果,而不是预先缓冲此查询的所有行。其他DBAPI在使所有行可用之前预缓冲所有行。原始数据库行的内存使用远小于ORM映射对象的内存使用,但在进行基准测试时仍应予以考虑。

我真的很难理解yield_per()的工作原理以及使用此方法的问题究竟是什么。另外,解决这些问题的正确方法是什么,并继续使用此函数迭代大量的行。

我对您拥有的所有建设性信息感兴趣,但这里有一些提示问题:

  • 如何存在同一行的多个实例?只通过关系(如果迭代表的两行有一个FK到另一个表中的同一行)?如果您不知道它发生了或者您只阅读关系中的属性,是否存在问题?
  • 懒惰='加入'或'子查询'是不可能的,但为什么呢?它们都只是您调用yield_per()的查询的一部分。
    • 如果在后续结果批次中清除它们,则只需再次加载它。那问题出在哪里?或者,如果做出改变,你唯一的问题就是你失去了关系的变化吗?
    • 在“子查询”加载的情况下,为什么要获取所有行? SQL Server可能需要保存一个大表,但为什么不直接将整个查询中的结果一个接一个地返回呢?
    • yield_per() doc q = sess.query(Object).yield_per(100).options(lazyload('*'), joinedload(Object.some_related))的示例中,他们停用了lazyload('*')的eagerload,但保留了一个加入的加载。有没有办法继续使用yield_per()和eagerload?有什么条件?
  • 他们说psycopg2是唯一支持流结果的DBAPI。这是唯一可以与yield_per()一起使用的DBAPI吗?据我所知yield_per使用DBAPI的cursor.fetchmany()example)函数支持其中许多函数。据我所知cursor.fetchmany()支持只获取部分结果并且不提取所有内容(如果它会获取所有内容,为什么函数存在?)
  • 如果您只进行读取访问(例如统计信息),我觉得yield_per()完全安全(即使使用eagerload)。这是对的吗?

2 个答案:

答案 0 :(得分:5)

如果您尝试将这些问题与yield_per一起使用,那么两个有问题的加载策略都会引发异常,因此您不必太担心。

相信 subqueryload唯一的问题是第二个查询的批量加载(尚未实现)。没有什么会在语义上出错,但如果你使用yield_per,你可能有一个很好的理由不想一次加载所有结果。所以SQLAlchemy礼貌地拒绝违背你的意愿。

joinedload更加微妙。仅在集合的情况下禁止它,其中主行可能具有多个关联的行。假设您的查询生成这样的原始结果,其中A和B是来自不同表的主键:

 A | B 
---+---
 1 | 1 
 1 | 2 
 1 | 3 
 1 | 4 
 2 | 5 
 2 | 6 

现在您使用yield_per(3)获取这些内容。问题是SQLAlchemy只能限制提取的数量,但它必须返回对象。在这里,SQLAlchemy只能看到前三行,因此它创建了一个{1}}对象,其中包含键1和三个 A子项:1,2和3。

当它加载下一批时,它想要用键1 ...啊创建一个新的B对象,但它已经有一个,所以不需要再创建它。额外的A,4将丢失。 (所以,不,即使使用B阅读已加入的集合也不安全 - 您的数据块可能会丢失。)

你可能会说“好吧,只要继续阅读行,直到你有一个完整的对象” - 但如果那个yield_per有一百个孩子怎么办?还是一百万? SQLAlchemy不能合理地保证它可以做你所要求的产生正确的结果,所以它拒绝尝试。

请记住,DBAPI的设计使得任何数据库可以与相同的API一起使用,即使该数据库不支持所有DBAPI功能也是如此。考虑到DBAPI是围绕游标设计的,但MySQL实际上并没有拥有游标!用于MySQL的DBAPI适配器必须伪造它们。

因此,当A 工作时,您可以从the MySQLdb source code看到它无法从服务器延迟获取;它将所有内容提取到一个大列表中,然后在您调用cursor.fetchmany(100)时返回一个切片。

fetchmany支持的是真正的流式传输,其中结果在服务器上记住持久,并且您的Python进程一次只能看到其中的一些。

您仍然可以将psycopg2yield_per或任何其他DBAPI一起使用;这就是DBAPI设计的重点。您将不得不为DBAPI中隐藏的所有原始行支付内存成本(这些是元组,相当便宜),但您不会 必须为所有ORM对象付费在同一时间。

答案 1 :(得分:-4)

请注意,有时yield_per会失败,不确定根是否为yield_per。 (您可以通过将收益率数字设置为幻数来进行测试,例如51、17)

sqlalchemy.exc.ProgrammingError:(_mysql_exceptions.ProgrammingError)(2014,“命令不同步;您现在不能运行此命令”)

数据库:mysql:5.7.0