如何选择事务隔离级别?
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 项目,将它们移动到列表内或跨列表。
运行这些操作时,为了保证索引的一致性,我做了如下处理:
创建项目:
- 计算此列表中的项目
SELECT COUNT(DISTINCT "Item"."id") as "cnt"
FROM "item" "Item"
WHERE "Item"."listId" = ${listId}
- 插入新项目,索引设置为从第 1 步开始计数:
INSERT INTO "item"("id", "listId", "index", "title", ...)
VALUES (${id}, ${listId}, ${count}, ${title})
这样索引会随着插入列表中的每个项目而增长。
移动项目:
- 检索项目的当前 listId 和 index:
SELECT "Item"."listId" AS "Item_listId", "Item"."index" AS "Item_index"
FROM "item" "Item"
WHERE "Item"."id" = ${id}
- 如有必要,更改 "shifted" 项目的索引,以便顺序一致,例如鉴于该项目向前移动,其当前位置(不包括)和其下一个位置(包括)之间的所有项目都需要将其 index 减少 1:
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}
问题是:
我应该为这些操作中的每一个提供什么事务隔离级别?保持索引列一致,没有间隙,最重要的是 - 没有重复对我来说非常重要。我是否理解 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" 某些数据集被放置(通过数据库或共享内存池),以便在任何给定时间只有一个用户可以更改它们,或者,提供一个工作流来管理冲突操作(很像版本控制系统)。如果两者均未完成,则会保留操作日志以了解发生了什么,并在以后检测到问题时予以纠正。
我在数据库中有一个 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 项目,将它们移动到列表内或跨列表。 运行这些操作时,为了保证索引的一致性,我做了如下处理:
创建项目:
- 计算此列表中的项目
SELECT COUNT(DISTINCT "Item"."id") as "cnt"
FROM "item" "Item"
WHERE "Item"."listId" = ${listId}
- 插入新项目,索引设置为从第 1 步开始计数:
INSERT INTO "item"("id", "listId", "index", "title", ...)
VALUES (${id}, ${listId}, ${count}, ${title})
这样索引会随着插入列表中的每个项目而增长。
移动项目:
- 检索项目的当前 listId 和 index:
SELECT "Item"."listId" AS "Item_listId", "Item"."index" AS "Item_index"
FROM "item" "Item"
WHERE "Item"."id" = ${id}
- 如有必要,更改 "shifted" 项目的索引,以便顺序一致,例如鉴于该项目向前移动,其当前位置(不包括)和其下一个位置(包括)之间的所有项目都需要将其 index 减少 1:
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}
问题是:
我应该为这些操作中的每一个提供什么事务隔离级别?保持索引列一致,没有间隙,最重要的是 - 没有重复对我来说非常重要。我是否理解 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" 某些数据集被放置(通过数据库或共享内存池),以便在任何给定时间只有一个用户可以更改它们,或者,提供一个工作流来管理冲突操作(很像版本控制系统)。如果两者均未完成,则会保留操作日志以了解发生了什么,并在以后检测到问题时予以纠正。