我在数据库中有一个表,负责存储有序/可重排序列表。它具有以下形状:
| id | listId | index | title | ... |
其中 id 是主键, listId 是外键,用于标识该项所属的列表,标题和其他列是项目。 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" | ...
用户可以创建/删除项目,将其移动到列表内或列表之间。 为了确保在运行这些操作时索引的一致性,请执行以下操作:
创建项目:
SELECT COUNT(DISTINCT "Item"."id") as "cnt"
FROM "item" "Item"
WHERE "Item"."listId" = ${listId}
INSERT INTO "item"("id", "listId", "index", "title", ...)
VALUES (${id}, ${listId}, ${count}, ${title})
这样,索引会随着列表中插入的每个项目而增长。
移动项目:
SELECT "Item"."listId" AS "Item_listId", "Item"."index" AS "Item_index"
FROM "item" "Item"
WHERE "Item"."id" = ${id}
UPDATE "item"
SET "index" = "index" - 1
WHERE "listId" = ${listId}
AND "index" BETWEEN ${sourceIndex + 1} AND ${destinationIndex}
我将忽略列表之间的变化,因为它非常相似。
UPDATE "item"
SET "index" = ${destinationIndex}
WHERE "id" = ${id}
删除项目:
检索项目的索引和listId
将同一列表中所有与此项目相邻的项目移回1步,以消除空白
UPDATE "item"
SET "index" = "index" - 1
WHERE "listId" = ${listId}
AND "index" > ${itemIndex}
DELETE FROM "item"
WHERE "id" = ${id}
问题是:
我应该为这些操作中的每一个提供什么事务隔离级别?对我来说,保持索引列的一致性,没有空隙以及最重要的是-没有重复是非常重要的。我是否正确理解创建项目操作必须进行幻像读取,因为它会根据某些条件对项目进行计数,并且应该可以可序列化?那其他操作呢?
答案 0 :(得分:1)
根据您的约束,可以在两列上创建唯一索引: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
。 加上事务隔离级别:“可重复读取”,如果唯一约束失败,或者我建议该语句返回一条记录,这将满足您的要求,然后回滚并重复该事务。
答案 1 :(得分:1)
在不了解您的特定应用程序的情况下,最安全的选择确实是在每次访问该表时都使用 serializable 作为隔离级别,但即使是该级别对于您的特定情况也可能不够。
(listId,索引)上的唯一约束可以防止重复(标题是什么?可以在同一列表中重复吗?),有些可以精确制作< strong>“看门狗”查询可以进一步缓解问题,数据库序列或存储过程可以确保没有漏洞,但事实是该机制本身似乎很脆弱。
仅了解您特定的问题,您似乎遇到的是用户级别的并发问题,即几个用户可以同时访问相同的对象并对其进行更改。假设这是您的典型Web应用程序,它具有无状态的后端(因此具有固有的分布式),那么在反映架构甚至功能要求的用户体验方面,这可能会带来很多影响。例如,假设用户 Foo 将项目 Car 移到了列表B ,该列表当前由用户 Bar 处理。因此,可以合法地假设操作完成后 Bar 将需要查看项目 Car ,但是除非有某种机制可以立即通知,否则不会发生更改的列表B 的用户。您在同一组列表上使用的用户越多,即使有了通知,情况也会变得更糟,因为您会收到越来越多的通知,直到用户看到事物一直在变化并且无法跟上
任何人都会为您提供答案的假设很多。我个人让我指出,您可能需要修改该应用程序的要求,或者确保管理层了解一些限制并接受它们。 这种类型的问题在分布式应用程序中非常普遍。通常,将“锁”放置在某些数据集上(通过数据库或共享内存池),以便在任何给定时间只有一个用户可以更改它们,或者提供工作流来管理冲突的操作(与版本控制系统非常相似) 。如果两者都不做,则会保留操作日志,以了解发生了什么,并在以后解决问题时予以纠正。