【问题标题】:How to pick transaction isolation levels?如何选择事务隔离级别?
【发布时间】:2019-12-02 21:02:15
【问题描述】:

我在数据库中有一个表,负责存储已排序/可重新排序的列表。它具有以下形状:

| id | listId | index | title | ... |

其中 id 是主键,listId 是外键,用于标识项目所属的列表,title 和其他列是项目。 index 属性负责列表中项目的位置。它是一个整数计数器(从 0 开始),在列表范围内是唯一的,但可以跨列表重复。示例数据:

| id      | listId  | index | title    | ...
---------------------------------------------
| "item1" | "list1" | 0     | "title1" | ...
| "item2" | "list1" | 1     | "title2" | ...
| "item3" | "list1" | 2     | "title3" | ...
| "item4" | "list2" | 0     | "title4" | ...
| "item5" | "list2" | 1     | "title5" | ...

用户可以创建/删除项目,在列表内或跨列表移动它们。 为了在运行这些操作时确保索引的一致性,我做了以下操作:

创建项目:

  1. 统计此列表中的项目
SELECT COUNT(DISTINCT "Item"."id") as "cnt" 
FROM "item" "Item" 
WHERE "Item"."listId" = ${listId}
  1. 插入新项目,索引设置为从第 1 步开始计数:
INSERT INTO "item"("id", "listId", "index", "title", ...) 
VALUES (${id}, ${listId}, ${count}, ${title})

这样索引随着插入列表中的每个项目而增长。

移动项目:

  1. 检索项目的当前 listIdindex
SELECT "Item"."listId" AS "Item_listId", "Item"."index" AS "Item_index" 
FROM "item" "Item" 
WHERE "Item"."id" = ${id}
  1. 如有必要,更改“移位”项目的索引,以便顺序一致,例如鉴于项目向前移动,其当前位置(不包括)和下一个位置(包括)之间的所有项目都需要将它们的 index 减少 1:
UPDATE "item" 
  SET "index" = "index" - 1 
WHERE "listId" = ${listId} 
  AND "index" BETWEEN ${sourceIndex + 1} AND ${destinationIndex}

我将省略跨列表移动的变化,因为它非常相似。

  1. 更新项目本身:
UPDATE "item" 
   SET "index" = ${destinationIndex} 
WHERE "id" = ${id}

删除项目

  1. 检索项目的索引和listId

  2. 将同一列表中与该项目相邻的所有项目向后移动 1 步,以消除间隙

UPDATE "item" 
  SET "index" = "index" - 1 
WHERE "listId" = ${listId} 
  AND "index" > ${itemIndex}
  1. 删除项目:
DELETE FROM "item" 
WHERE "id" = ${id}

问题是:

我应该为这些操作中的每一个提供哪些事务隔离级别?对我来说,保持索引列一致、没有间隙、最重要的是 - 没有重复是非常重要的。我是否理解 create item 操作会受到幻读的影响,因为它按某些标准对项目进行计数,并且应该是 serializable?其他操作呢?

【问题讨论】:

  • 无论您选择何种隔离级别以及如何准确地更改表中的数据,您都应该使用(ListID, Index) 上的唯一索引强制执行最重要的约束 - 没有重复项。唯一索引约束很容易实现,并且将始终保持不变。
  • 删除记录时,降低所有ID大于删除记录1的记录的ID,在数据量较大时可能会导致性能问题。我的建议是,1. 创建一个序列对象并将其用于新 ID。不要对 Id 列进行“自动递增”并使用序列手动管理它们。 2. 删除记录时,将Id大于被删除记录Id的记录的Ids减1。同一个事务中的Sequence值减1。 3.使用'Read uncommitted'类型作为隔离级别。
  • 我警告你,如果你在 UI 上使用 Id 进行操作,在删除记录时更新 Id 会给你带来巨大的操作问题。因为,其他人可以同时编辑另一个具有更大 Id 值的记录。
  • @Gurcan 我需要 ids 是持久的/顺序独立的,并且还希望它们是 uuids 而不是整数。所以 id 和 index 是独立的列。递增/递减记录索引对我来说不是问题,因为一列不打算包含超过 10 个项目。
  • 所以,基本设计看起来还不错。索引序列机制可以是我建议的方式。你同意吗?

标签: sql postgresql concurrency transactions acid


【解决方案1】:

在不了解您的特定应用程序的情况下,最安全的选择确实是在您访问该表时使用 serializable 作为隔离级别,但即使该级别也可能不足以满足您的具体情况。

(listId, index) 上的unique 约束可以防止重复(标题怎么样?可以在同一个列表中重复吗?),有些是精心设计的 “看门狗”查询可以进一步缓解问题,数据库序列或存储过程可以确保没有间隙,但事实是机制本身似乎很脆弱。

仅了解您的特定问题的这么多,您似乎遇到的是用户级别的并发问题,即多个用户可以同时访问相同的对象并对其进行更改。假设这是您的典型 Web 应用程序,具有无状态后端(因此固有分布式),这可能会在反映架构甚至功能要求的用户体验方面产生大量影响。例如,用户 Foo 将项目 Car 移动到当前由用户 Bar 处理的 List B。然后可以合理地假设 Bar 需要在操作完成后立即查看项目 Car,但除非有某种机制可以立即通知,否则不会发生这种情况列表 B 的用户进行了更改。您在同一组列表上工作的用户越多,即使收到通知也会变得越糟糕,因为您将拥有越来越多的通知,直到用户看到事情一直在变化而无法跟上它.

为了给你答案,任何人都会做出很多假设。我自己引导我声明您可能需要修改该应用程序的要求或确保管理层了解一些限制并且他们接受它们。 这种类型的问题在分布式应用程序中很常见。通常对某些数据集进行“锁定”(通过数据库或共享内存池),以便在任何给定时间只有一个用户可以更改它们,或者提供工作流来管理冲突操作(很像版本控制系统) .如果两者均未完成,则会保留操作日志以了解发生的情况并在以后发现问题时予以纠正。

【讨论】:

  • 该应用程序是一个类似 trello 的板。更新被传送到前端实时 wia websocket,并且由于板限制为约 20 个用户,因此我不希望多个用户同时移动同一张卡时会发生很多冲突。另一方面,前端渲染/用户界面逻辑依赖于一致的索引顺序,重复的索引或间隙会导致未定义的行为。保证后端的这种一致性然后尝试让前端意识到可能的不一致似乎更干净。
  • 在这种情况下,“可序列化”可能过多。尝试使用“可重复读取”,但同时在服务器端(而不是数据库端)实现一些逻辑,以序列化在同一块板上完成的操作。一种方法是为每个“当前活动”板构造一个对象,然后在该对象上使用 synchronized 来阻止用户同时获得访问权限,从而序列化操作。另一种(可能更好)的方法是使用某种队列机制来序列化操作并在 websocket 上返回反馈。
【解决方案2】:

根据您的限制,您可以在两列上创建唯一索引:listId,index 可以定义为唯一的。这将避免重复。

另外,为了避免空白,我建议:

select listId, index, (select min(index) from Item i2 where listId = :listId and i2.index > i1.index) as nextIndex from Item i1 where nextIndex - index > 1 and listId = :listId

在每笔交易结束时。 连同事务隔离级别:“可重复读取”,如果唯一约束失败,或者我建议的语句返回记录,则回滚并重复事务,这应该符合您的要求。

【讨论】:

  • 使用选择条件作为某种“模拟约束”的想法很聪明,没有想到。虽然不幸的是唯一索引不起作用,因为列并不完全唯一。我在 2 次写入操作中移动/删除它们(首先移动相邻项目,然后移动/删除项目本身),操作 1 将导致违反此索引
  • 根据 dbms,可能会在事务结束时检查唯一键约束(请参阅:postgresql.org/docs/9.1/index-unique-checks.html),那么移动不会有问题。否则,扩展建议的 select 语句以检测相同的索引但不同的 id。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-09-30
  • 2015-07-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-08-27
相关资源
最近更新 更多