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

F.R*_*aab 21 python mysql orm sqlalchemy

引用SQLalchemy文档:

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

警告

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

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

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

我真的有一个问题,了解如何yield_per()工作以及使用此方法的问题究竟是什么.还有什么是解决这些问题的正确方法,并继续使用此函数迭代大量的行.

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

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

Eev*_*vee 6

如果您尝试使用它们yield_per,两个有问题的加载策略都会引发异常,因此您不必太担心.

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

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

 A | B 
---+---
 1 | 1 
 1 | 2 
 1 | 3 
 1 | 4 
 2 | 5 
 2 | 6 
Run Code Online (Sandbox Code Playgroud)

现在你用它来取这些yield_per(3).问题是SQLAlchemy只能限制它按提取的数量,但它必须返回对象.在这里,SQLAlchemy只能看到前三行,因此它创建了一个A带有键1和三个 B子节点的对象:1,2和3.

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

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


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

因此,尽管cursor.fetchmany(100)工作,你可以看到MySQLdb源代码,它并不会从服务器获取懒洋洋的; 它将所有内容提取到一个大列表中,然后在您调用时返回一个切片fetchmany.

什么psycopg2支持是真正的流媒体,其中结果被记住坚持在服务器上,并且你的Python程序只能看到他们几个人在一个时间.

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