使用SQLAlchemy,如何将行转换为" real" Python对象?

时间:2017-06-16 16:28:54

标签: python sqlalchemy

我一直在使用SQLAlchemy和Alembic来简化我使用的数据库访问,以及我对表所做的任何数据结构更改。这个问题一直很顺利,直到我开始注意到SQLAlchemy" expiring"越来越多的问题。从我的观点来看几乎是随机的。

一个恰当的例子就是这个片段,

class HRDecimal(Model):
    dec_id = Column(String(50), index=True)

    @staticmethod
    def qfilter(*filters):
        """
        :rtype : list[HRDecimal]
        """
        return list(HRDecimal.query.filter(*filters))


class Meta(Model):
    dec_id = Column(String(50), index=True)

    @staticmethod
    def qfilter(*filters):
        """
        :rtype : list[Meta]
        """
        return list(Meta.query.filter(*filters))

代码:

ids = ['1', '2', '3']  # obviously fake list of ids

decs = HRDecimal.qfilter(
    HRDecimal.dec_id.in_(ids))
metas = Meta.qfilter(
    Meta.dec_id.in_(ids))
combined = []

for ident in ids:
    combined.append((
        ident,
        [dec for dec in decs if dec.dec_id == ident],
        [hm for hm in metas if hm.dec_id == ident]
    ))

对于上述问题,没有问题,但是当我处理可能包含几千个ID的ID列表时,此过程开始耗费大量时间,如果从在烧瓶的网页请求中,线程经常被杀死。

当我开始讨论为什么发生这种情况时,关键领域是

        [dec for dec in decs if dec.dec_id == ident],
        [hm for hm in metas if hm.dec_id == ident]

在组合这些(我认为是)Python对象的某个时刻,在SQLAlchemy代码中调用dec.dec_idhm.dec_id最好 ,我们进入,

def __get__(self, instance, owner):
    if instance is None:
        return self

    dict_ = instance_dict(instance)
    if self._supports_population and self.key in dict_:
        return dict_[self.key]
    else:
        return self.impl.get(instance_state(instance), dict_)

InstrumentedAttribute sqlalchemy/orm/attributes.py def get(self, state, dict_, passive=PASSIVE_OFF): """Retrieve a value from the given object. If a callable is assembled on this object's attribute, and passive is False, the callable will be executed and the resulting value will be set as the new value for this attribute. """ if self.key in dict_: return dict_[self.key] else: # if history present, don't load key = self.key if key not in state.committed_state or \ state.committed_state[key] is NEVER_SET: if not passive & CALLABLES_OK: return PASSIVE_NO_RESULT if key in state.expired_attributes: value = state._load_expired(state, passive) 似乎非常慢,但更糟糕的是,我观察了字段过期的时间,然后我们进入,

AttributeImpl

同一文件中的session-options。这里的可怕问题是state._load_expired完全重新运行 SQL查询。因此,在这样的情况下,我们最终会运行成千上万的小小的#34;对数据库的SQL查询,我认为我们应该只运行两个" large"在顶部。

现在,我已经解决了过期的问题,即如何使用app = Flask(__name__) CsrfProtect(app) db = SQLAlchemy(app) 初始化烧瓶数据库,更改

app = Flask(__name__)
CsrfProtect(app)
db = SQLAlchemy(
    app,
    session_options=dict(autoflush=False, autocommit=False, expire_on_commit=False))

href="#/drill"

这肯定改善了上述情况,因为行字段似乎随机地(根据我的观察结果)随机过期,但是#34; normal"访问SQLAlchemy项目的速度仍然是我们当前正在运行的问题。

有没有办法使用SQLAlchemy来获得真正的"真实的"从查询返回的Python对象,而不是像现在这样的代理,所以它不受此影响吗?

1 个答案:

答案 0 :(得分:4)

您的随机性可能与在不方便的时候明确提交或回滚,或者由于某种类型的自动提交有关。在其默认配置SQLAlchemy会话expires all ORM-managed state when a transaction ends中。这通常是件好事,因为当交易结束时你不知道DB的当前状态是什么。您可以使用expire_on_commit=False完成此操作。

正如here所解释的那样,ORM也不适合一般的超大型批量操作。它非常适合处理复杂的对象图并将这些图持久化到关系数据库,而且您可以轻松地为您组织所需的插入等。其中一个重要部分是跟踪对实例属性的更改。 SQLAlchemy Core更适合批量生产。

您似乎正在执行2个产生大量结果的查询,然后按照"关于数据,但是以一种相当不正常的方式,因为对于每个id,你有扫描整个结果列表,或者 O(nm),其中n是id的数量,m是结果。相反,您应该首先按id将结果分组到对象列表,然后执行" join"。在其他一些数据库系统上,您可以直接在SQL中处理分组,但是除了JSON之外,MySQL没有数组的概念。

可能更高效的分组版本可能是:

from itertools import groupby
from operator import attrgetter

ids = ['1', '2', '3']  # obviously fake list of ids

# Order the results by `dec_id` for Python itertools.groupby. Cannot
# use your `qfilter()` method as it produces lists, not queries.
decs = HRDecimal.query.\
    filter(HRDecimal.dec_id.in_(ids)).\
    order_by(HRDecimal.dec_id).\
    all()

metas = Meta.query.\
    filter(Meta.dec_id.in_(ids)).\
    order_by(Meta.dec_id).\
    all()

key = attrgetter('dec_id')
decs_lookup = {dec_id: list(g) for dec_id, g in groupby(decs, key)}
metas_lookup = {dec_id: list(g) for dec_id, g in groupby(metas, key)}

combined = [(ident,
             decs_lookup.get(ident, []),
             metas_lookup.get(ident, []))
            for ident in ids]

请注意,因为在这个版本中我们只对查询进行一次迭代,all()并非绝对必要,但它也不应该受到太大影响。也可以在不使用defaultdict(list)的SQL进行排序的情况下完成分组:

from collections import defaultdict

decs = HRDecimal.query.filter(HRDecimal.dec_id.in_(ids)).all()
metas = Meta.query.filter(Meta.dec_id.in_(ids)).all()

decs_lookup = defaultdict(list)
metas_lookup = defaultdict(list)

for d in decs:
    decs_lookup[d.dec_id].append(d)

for m in metas:
    metas_lookup[m.dec_id].append(m)

combined = [(ident, decs_lookup[ident], metas_lookup[ident])
            for ident in ids]

最后回答你的问题,你可以获取"真实" Python对象通过查询Core表而不是ORM实体:

decs = HRDecimal.query.\
    filter(HRDecimal.dec_id.in_(ids)).\
    with_entities(HRDecimal.__table__).\
    all()

将生成一个namedtuple like objects列表,可以使用_asdict()轻松转换为dict。