如何选择事务隔离级别?

How to pick transaction isolation levels?

我在数据库中有一个 table 负责存储 ordered/reorderable 列表。它具有以下形状:

| 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" | ...

用户可以 create/delete 项目,将它们移动到列表内或跨列表。 运行这些操作时,为了保证索引的一致性,我做了如下处理:

创建项目:

  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. 如有必要,更改 "shifted" 项目的索引,以便顺序一致,例如鉴于该项目向前移动,其当前位置(不包括)和其下一个位置(包括)之间的所有项目都需要将其 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可以定义为唯一。这将避免重复。

此外,为了避免差距,我建议:

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

在每笔交易结束时。 连同事务隔离级别:"Repeatable Read" 如果唯一约束失败,或者我建议的语句返回一条记录,则回滚并重复事务,这应该满足您的要求。

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

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

只知道这么多你的具体问题,你似乎遇到了用户级别的并发问题,因为多个用户可以同时访问同一个 objects 并对其进行更改.假设这是典型的 web-application 和无状态 back-end(因此本质上是分布式的),这可能会在反映架构甚至功能需求的用户体验方面产生大量影响。例如,用户 Foo 将项目 Car 移动到用户当前正在处理的 List B 小节。然后可以合理地假设 Bar 将需要在操作完成后立即查看项目 Car,但这不会发生,除非有一些立即通知 List B 用户更改的机制。您在同一组列表上工作的用户越多,即使有通知也会变得越糟,因为您会收到越来越多的通知,直到用户看到事情一直在变化并且无法跟上它.

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