SqlAlchemy 针对只读对象模型的优化
我有一个复杂的对象网络,这些对象是通过sqlite数据库和sqlalchemy这个工具生成的。我有很多层次很深的对象:
for parent in owner.collection:
for child in parent.collection:
for foo in child.collection:
do lots of calcs with foo.property
我的性能分析显示,在这种情况下,sqlalchemy的监控功能花费了很多时间。
问题是:我在运行时从来不改变对象模型(映射的属性),所以一旦这些对象加载完成,我其实不需要监控功能,或者说sqlalchemy的任何额外开销。经过很多研究,我在想我可能需要从已经加载的“监控对象”中克隆出一套“纯python”的对象,但这会很麻烦。
在这里,性能非常关键(这是一个模拟器),所以也许直接使用sqlite的api,把这些层写成C扩展会更好。你觉得怎么样?
3 个回答
-1
试着用一个查询加上JOIN来代替Python中的循环。
0
你可以关闭相关关系的懒加载,这样SQLAlchemy就会一次性把它们全部取出来。
10
如果你经常需要引用一个对象的某个属性,使用一个本地变量来存储这个属性是个简单的技巧。
如果你想要一种方法来创建便宜的纯Python克隆,可以和原始对象共享字典对象:
class CheapClone(object):
def __init__(self, original):
self.__dict__ = original.__dict__
像这样创建的副本大约只花费了访问属性的一半时间,而且查找属性的速度和正常一样快。
也许还有一种方法可以让映射器创建一个未被监控的类的实例,而不是被监控的那个。如果我有时间,我可能会看看这个假设有多深入,即填充的实例和被监控的类是同一种类型。
我找到了一种快速且简单的方法,似乎在0.5.8和0.6版本上至少能部分有效。没有测试继承或其他可能会产生不良影响的特性。此外,这涉及一些非公开的API,所以在更改版本时要小心可能会出问题。
from sqlalchemy.orm.attributes import ClassManager, instrumentation_registry
class ReadonlyClassManager(ClassManager):
"""Enables configuring a mapper to return instances of uninstrumented
classes instead. To use add a readonly_type attribute referencing the
desired class to use instead of the instrumented one."""
def __init__(self, class_):
ClassManager.__init__(self, class_)
self.readonly_version = getattr(class_, 'readonly_type', None)
if self.readonly_version:
# default instantiation logic doesn't know to install finders
# for our alternate class
instrumentation_registry._dict_finders[self.readonly_version] = self.dict_getter()
instrumentation_registry._state_finders[self.readonly_version] = self.state_getter()
def new_instance(self, state=None):
if self.readonly_version:
instance = self.readonly_version.__new__(self.readonly_version)
self.setup_instance(instance, state)
return instance
return ClassManager.new_instance(self, state)
Base = declarative_base()
Base.__sa_instrumentation_manager__ = ReadonlyClassManager
使用示例:
class ReadonlyFoo(object):
pass
class Foo(Base, ReadonlyFoo):
__tablename__ = 'foo'
id = Column(Integer, primary_key=True)
name = Column(String(32))
readonly_type = ReadonlyFoo
assert type(session.query(Foo).first()) is ReadonlyFoo