【问题标题】:Append only jsonb column in Rails + Postgres在 Rails + Postgres 中仅附加 jsonb 列
【发布时间】:2017-05-12 05:22:50
【问题描述】:

是否有人使用 Rails 4.x 或 Rails 5.x 以及 Postgres (9.4+) jsonb 专栏?我了解使用 jsonb + Postgres 的好处——您可以将无模式数据与结构化数据混合。有些人可能会称这是一种快乐的媒介。

我们注意到更新行时很容易(无意或有意)覆盖jsonb 数据。有没有人看到任何使jsonb 列仅附加的技术?

假设一行有一个jsonbcolumn,我们称之为“问卷”。问卷栏的生命周期如下:

  1. null 开头
  2. 一些json被保存(通过UPDATE操作)
  3. 另一个UPDATE操作发生,这次追加更多的json是一个json数组
  4. 发生另一个UPDATE 操作,但这次使用的是有效 json 的“{}”
  5. “问卷”中存储的所有数据现在都无效。

这很糟糕,因为我们基本上丢失了所有数据。

更广泛地说,其他人如何在 Rails 或 Postgres 中实现仅附加表(或列)?这个问题最好在数据库层还是应用层解决?

欣赏这些想法。提前致谢!

【问题讨论】:

  • 覆盖jsonb 列比覆盖varcharint4 列更容易吗?

标签: ruby-on-rails json postgresql jsonb


【解决方案1】:

如您所见,json[b] 值(就像 PostgreSQL 中的任何其他类型一样)只能作为一个整体使用 UPDATEed。

8.14.2. 有效设计 JSON 文档:

当存储在表中时,JSON 数据与任何其他数据类型一样受到相同的并发控制注意事项。尽管存储大文档是可行的,但请记住,任何更新都会获取整行的行级锁。考虑将 JSON 文档限制在可管理的大小,以减少更新事务之间的锁争用。 理想情况下,每个 JSON 文档都应该代表一个原子数据,业务规则规定不能合理地进一步细分为更小的数据可以独立修改

因此,一个明显的解决方案是划分 JSON 数组并存储其元素(例如,在联结表中,与原始表具有一对多关系)。

但是,您也可以通过其他几种方式避免这些“丢失的更新”(但这些方式确实不是那些理想的方式)。

  1. 原子UPDATEs

让我给你介绍一个类比。如果你想在任何 RDBMS 中创建一个计数器,你通常这样做:

UPDATE counter SET value = value + 1

而且这(当然)不会丢失更新。但是,当你这样做时

SELECT value FROM counter
-- do something in client & bind the selected value + 1 to the next query:
UPDATE counter SET value = ?

会丢失更新。因为,在SELECTUPDATE 语句之间,另一个事务可以更新当前事务之前的值。如果发生这种情况,那些UPDATEs 就会丢失。您最有可能在您的jsonb 列中使用这种UPDATE

第一个语句的 jsonb 对应项可能如下所示:

-- to append a JSON array element to the root JSON array
UPDATE t SET jsonb_col = jsonb_col || '[{"a":1}]'; 
-- to append a JSON array element to an array, located on the path: 'a' (requires 9.6+)
UPDATE t SET jsonb_col = jsonb_insert(jsonb_col, ARRAY['a', '-1'], '{"a":1}', TRUE);
-- Notes: TRUE means that insert AFTER ... -1 (in the path) means after the LAST ELEMENT

但是,这些(通常)很难通过 ORM 实现。

  1. 锁定

如果您不能使用上述查询,那么您必须确保一次只有一个事务可以UPDATE 表中的一行。

2/A。 悲观锁定

这样,您可以明确地告诉 RDBMS 您 SELECTed 一行是出于特定原因:FOR UPDATE。前任ActiveRecord supports this.

2/B。 乐观锁定

这样,您必须在UPDATE 中使用/包含version 列,即:

UPDATE t
SET    jsonb_col = ?,
       t_version = t_version + 1
WHERE  t_version = ?

这样,就无法释放UPDATE,但您的语句可能根本没有任何作用。您必须自己检查行数(在您的客户端中),如果没有更新任何行,请重试。

F.ex. ActiveRecord supports this too.

阅读更多内容:Optimistic vs. Pessimistic locking

  1. 可序列化事务

Serializable transactions 的工作方式类似于基于乐观锁定 的解决方案,但它不需要特殊的版本列。相反,RDBMS 将使用谓词锁定来避免丢失更新。此外,当发生序列化失败时,您应该重试整个事务。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-07-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多