SqlAlchemy单表查询过滤之谜
发生背景
假设我有两个表,A 和 B
A表结构如下:
id | content |
---|---|
1 | 测试1 |
2 | 测试2 |
B表结构如下:
id | aid | content |
---|---|---|
1 | 1 | 测试1 |
2 | 1 | 测试2 |
3 | 2 | 测试3 |
4 | 1 | 测试4 |
5 | 2 | 测试5 |
现在我有了两个模型
|
|
奇特的一幕
当我连接A,B两个表时,发生了奇怪的一幕。
|
|
深入探究
从 sql 的角度来说,查询出来应该一定是5条数据,于是只能深入查询 sqlalchemy
源码来探究了。
源码分析
A.query 一切都是继承 Query
,于是我们去查看 query.py
的源码
|
|
发现 all方法
是调用 list
,也就是说 query
对象是一个可迭代的对象。因此我们可以去看 __iter__
方法
|
|
_compile_context()
是处理sql
语句的方法。_execute_and_instances(context)
才是真正运行的sql
的语句。
|
|
- 经过调试,我们发现
result
的数据依然是 5 条。 - 因此处理后的数据应该是在
loading.instances
里面被处理的。 - 因此,我们继续追踪
loading.py
|
|
在这个函数里面我们需要注意如下几点:
filtered
参数.single_entity
参数.proc
函数util.unique_list
处理函数.
filtered 分析
我们是在什么时候设置 query._has_mapper_entities
。不难看出这个参数是来源于 query
对象,因此我们赶快去查看 query
函数。
|
|
Query
的_set_entities
发现它将_has_mapper_entities
设置成了 False- 因此我们只能寄希望于
_QueryEntity
了.
|
|
从 _QueryEntity
的 __new__
方法我们可以清晰的发现,它创建了一个 _MapperEntity
对象,在这个对象里面设置了 _has_mapper_entities
对象。
到这里,我们可以了解到:在处理 instantce
时我们的 filtered
为 True 了。
single_entity 分析
这个参数,其实就是看 query._entities
有多少个。
|
|
分析这个,还是从 Query
的 __init__
方法中分析。
|
|
- 当我们经过以上代码调试的时候,发起我们的
entities
只传入了一个A
对象。 - 当
_MapperEntity
对象被创建时,就已经设置了supports_single_entity
参数。
经过以上分析:我们可以得出我们的案例,属于单个对象查询。因此我们的过滤是:
proc 函数分析
从 loading.py
的 instances
中,我们可以分析出,proc
是来自于 _MapperEntity
对象的 row_processor
方法.
|
|
从这里可以看出 _instance
来自 loading._instance_processor
|
|
note: 我们这里先不谈其他的 _instance
处理,我们这里只选取了我们这个测试案例的运行轨迹。
最后我们可以发现,proc
函数就是这里的 _instance
函数。
从上面的 session_identity_map
和 identitykey
可以看出针对(类名 + 主键)的方式来存储缓存对象,
针对相同主键的ID和类会重复使用缓存对象。
用我们的案例也就是说,A(1) 有三条数据,A(2) 有两条数据。最后会生成一个含有(5)条数据的 list。
util.unique_list
处理函数
我们可以轻易的在 _collections.py
中,看到 unique_list
|
|
经过上面的分析,我们可以清楚的知道:
seq
就是我们最后得出的含有缓存对象的list
;hashfunc
其实就是内置的id
函数;- 同一对象的
hash
值是一致的;
综上所述:所以最后我们得出只含有两个对象的(list
)
总结
sqlalchemy
这样的做法,会使我们和SQL
之间产生疑惑。- 分析得出,
sqlalchemy
在做单entity
查询时,会对同一条数据的对象重复利用。