使用负索引加速 sqlalchemy orm 动态关系切片

speed up sqlalchemy orm dynamic relationship slicing with negative indicies

我有以下SQL一个模型和关系。我每秒记录每个通道的测量值,因此数据库中有很多测量值。

class Channel( Model ) :
    __tablename__   = 'channel'
    id              = Column( Integer, primary_key=True )
    #! --- Relationships ---
    measurements    = relationship( 'Measurement', back_populates='channel', lazy='dynamic' )

class Measurement( Model ) :
    __tablename__   = 'measurement'
    id              = Column( Integer, primary_key=True )
    timestamp       = Column( DateTime, nullable=False )
    value           = Column( Float, nullable=False )
    #! --- Relationships ---
    channel         = relationship( 'Channel', back_populates='measurements', uselist=False )

如果我想获得最新的测量值,我可以通过 ORM 获得它并使用 负索引.

进行切片
channel.measurements[-1]

但是,非常非常慢!!

我可以使用 .filter().order_by() 等进一步过滤关系查询,以获得我想要的,但我喜欢使用 ORM(为什么要不然呢?)

我注意到,如果我使用 正索引 进行切片,它会很快(类似于上面提到的显式 SQLA 查询)。

channel.measurements[0]

我更改了关系以保持 measurements 倒序,这似乎与使用零索引一起工作。

    measurements    = relationship( 'Measurement', back_populates='channel', lazy='dynamic', order_by='Measurement.id.desc()' )

那么,为什么负索引切片这么慢??

这是 SQLAlchemy 中的错误吗?我本以为做正确的 SQL 以仅从数据库中获取最新项目会足够聪明?

我还需要做些什么才能让测量值按自然顺序排序并使用负索引切片并获得与其他方法相同的速度吗??

你没有给出任何顺序,所以它必须将所有对象加载到一个列表中,然后获取最后一个。

如果加上echo=True参数,可以看出查询的区别:

对于 measurements[0],它 select 只是匹配通道的测量值之一 (LIMIT 1):

SELECT measurement.id AS measurement_id, measurement.ts AS measurement_ts,
  measurement.value AS measurement_value,
  measurement.channel_id AS measurement_channel_id
FROM measurement
WHERE %(param_1)s = measurement.channel_id
 LIMIT %(param_2)s
{'param_1': 6, 'param_2': 1}

对于 measurements[-1],它 select 是与频道匹配的所有测量值。你还没有订购它,所以它必须要求数据库以它决定的任何顺序 return 行(可能是 measurement 上的主键,但不能保证):

SELECT measurement.id AS measurement_id, measurement.ts AS measurement_ts,  
  measurement.value AS measurement_value,
  measurement.channel_id AS measurement_channel_id
FROM measurement
WHERE %(param_1)s = measurement.channel_id
{'param_1': 6}

如果您只想要最新的测量,select 它并按时间戳字段排序;您可能需要 channel_idtimestamp 字段上的索引:

db.session.query(Measurement)\
    .filter(Measurement.channel_id == channel_id)\
    .order_by(Measurement.ts.desc())\
    .limit(1)\
    .first()

似乎答案是 SQLA 不支持具有负索引的高效切片或关系集合。事实上,代码中似乎有一些笨拙的尝试,但由于没有仔细考虑,将从 SQLA 中删除。

https://github.com/sqlalchemy/sqlalchemy/issues/5605

我通过实施混合 属性 解决了我的问题,returns 我使用了最新的测量值,而不是直接切分关系集合。

    @hybrid_property
    def latest_measurement( self ) -> float :
        """
        Hybrid property that returns the latest measurement for the channel.
        """
        measurement = self.measurements.order_by( Measurement.id.desc() ).first()
        return measurement