如何在Elixir/SQLAlchemy中实现原子增减?

4 投票
1 回答
4484 浏览
提问于 2025-04-16 06:59

我想在一个Elixir实体中增加(或减少)一个分数字段:

class Posting(Entity):

  score = Field(Integer, PassiveDefault(text('0')))

  def upvote(self):
      self.score = self.score + 1

不过,这在同时进行的投票操作中并不总是有效。我能想到的最好的办法就是这个复杂的东西(基本上是用SQLAlchemy构建一个SQL更新语句):

def upvote(self):
    # sqlalchemy atomic increment; is there a cleaner way?
    update = self.table.update().where(self.table.c.id==self.id)
    update = update.values({Posting.score: Posting.score + 1})
    update.execute()

你觉得这个解决方案有什么问题吗?有没有更简单的方法可以实现同样的效果?

我希望在这里避免使用数据库锁。我正在使用Elixir、SQLAlchemy和Postgres。

更新

这是一个从vonPetrushev的解决方案衍生出来的变体:

def upvote(self):
    Posting.query.filter_by(id=self.id).update(
        {Posting.score: Posting.score + 1}
    )

这个比我最初的解决方案要好一些,但仍然需要过滤当前的实体。不幸的是,如果实体分布在多个表中,这个方法就不管用了。

1 个回答

2

我试试看,但不确定这是否符合你的需求:

session.query(Posting).\
    .filter(Posting.id==self.id)\
    .update({'score':self.score+1})

你可能想在这之后立刻执行 session.commit()?

编辑:关于问题的更新

如果 Posting 是从一个映射到多个表的类(Entity)派生出来的,那么上面的解决方案依然适用,但 Posting.id 属性的含义就变了,也就是说,它不再映射到某个表的列,而是映射到一个不同的组合。在这里,你可以查看如何定义它:http://docs.sqlalchemy.org/en/latest/orm/nonstandard_mappings.html#mapping-a-class-against-multiple-tables。我建议可以这样写:

    j = join(entity_table_1, entity_table_2)
    mapper(Entity, j, properties={
        'id': column_property(entity_table_1.c.id, entity_table_2.c.user_id)
        <... some other properties ...>
    })

撰写回答