Python 如何更新JSON类型列中的特定值

Python 如何更新JSON类型列中的特定值,python,postgresql,sqlalchemy,race-condition,Python,Postgresql,Sqlalchemy,Race Condition,我有我的web应用程序,在json类型列中有关于我的用户的统计信息。例如:{'current':{'friends':5,'wins':2,'loss':10}。我想在比赛条件下只更新特定的字段。现在我只是简单地更新整个字典,但当用户同时玩两个游戏时,可能会出现竞争情况 现在我是这样做的: 类用户: name=Column(Unicode(1024),null=False) username=Column(Unicode(128),nullable=False,unique=True,defau

我有我的web应用程序,在json类型列中有关于我的用户的统计信息。例如:
{'current':{'friends':5,'wins':2,'loss':10}
。我想在比赛条件下只更新特定的字段。现在我只是简单地更新整个字典,但当用户同时玩两个游戏时,可能会出现竞争情况

现在我是这样做的:

类用户:
name=Column(Unicode(1024),null=False)
username=Column(Unicode(128),nullable=False,unique=True,default='')
密码=列(Unicode(256),可空=真,默认值=“”)
计数器=列(
MutableDict.as_mutable(JSON),nullable=False,
server_default=text(“{}”),default=lambda:copy.deepcopy(默认_计数器))
def当前_计数器(自身、功能、编号):
current=self.counters.get('current',{})[feature]
如果当前+数字<0:
返回
self.counters.get('current',{})[feature]=current+number
self.counters.changed()
但这将在更改值后更新整个计数器列,如果发生两场比赛,我预计比赛条件

我在考虑一些
会话。查询
,类似的,但我不是那么好:

def更新计数器(自身、会话、功能、编号):
current=self.counters.get('current',{})[feature]
如果当前+数字<0:
返回
session.query(用户)\
.filter(User.id==self.id)\
.更新({
“当前”:func.jsonb_集(
User.counters['current'][功能],
列(当前)+列(编号),
"对")
},
同步会话=False
)
此代码生成:
NotImplementedError:此表达式不支持
Event.counters['current'][feature]
行的运算符“getitem”,但我不知道如何使其工作


感谢您的帮助。

此错误是通过链接项访问而产生的,而不是将索引元组用作单个操作:

User.counters['current', feature]
这将产生一个新的结果。但是如果这样做的话,您将只在嵌套的JSON中设置值,而不是在整个值中设置值。此外,从JSON索引的值是一个整数(而不是一个集合),因此
jsonb_set()
甚至不知道该做什么。这就是为什么
jsonb_set()
接受路径作为其第二个参数的原因,该参数是一个文本数组,描述了要在JSON中设置的值:

func.jsonb_set(User.counters, ['current', feature], ...)
至于比赛条件,可能还有一个。首先从中的当前模型对象获取计数

current = self.counters.get('current', {})[feature]
然后继续在更新中使用该值,但如果另一个事务在这两者之间执行了类似的更新,该怎么办?您可能会覆盖该更新的更改:

  select, counter = 42 |
                       | select, counter = 42
  update counter = 52  |                       # +10
                       | update counter = 32   # -10
  commit               |
                       | commit                # 32 instead of 42
然后,解决方案是确保使用
FOR UPDATE
获取当前模型对象,或者使用
SERIALIZABLE
事务隔离(准备在序列化失败时重试),或者忽略获取的值,让DB计算更新:

# Note that create_missing is true by default
func.jsonb_set(
    User.counters,
    ['current', feature],
    func.to_jsonb(
        func.coalesce(User.counters['current', feature].astext.cast(Integer), 0) +
        number))
如果要确保在结果为负值时不更新值(请记住,您之前读取的值可能已经更改),请使用DB计算值作为谓词添加检查:

def update_counter(self, session, feature, number):
    current_count = User.counters['current', feature].astext.cast(Integer)
    # Coalesce in case the count has not been set yet and is NULL
    new_count = func.coalesce(current_count, 0) + number

    session.query(User) \
        .filter(User.id == self.id, new_count >= 0) \
        .update({
            User.counters: func.jsonb_set(
               func.to_jsonb(User.counters),
               ['current', feature],
               func.to_jsonb(new_count)
            )
        }, synchronize_session=False)

谢谢你的回答,就是这样!