【发布时间】:2019-03-21 12:44:19
【问题描述】:
我需要存储一个任务项目表,其中每个项目都有一个唯一标识符。任务可以多次到达,因此标识符不是主键。然而,我只关心我使用序列识别的任务的最新版本。任务的每个实例都可以是NEW 或DONE。这些表看起来有点像这样:
CREATE SEQUENCE TASKSEQ;
CREATE TABLE TASKS (
ID VARCHAR2(100),
STATE VARCHAR2(50),
SEQ NUMBER(20)
);
作为数据模拟,假设该表包含一百万个完整的任务,但在将状态设置为 NEW 后,新一批先前存在的任务立即到达。
BEGIN
FOR IDX IN 1..1000000
LOOP
INSERT INTO TASKS (ID, STATE, SEQ)
VALUES (IDX, 'DONE', TASKSEQ.NEXTVAL);
END LOOP;
FOR IDX IN 900001..1000000
LOOP
INSERT INTO TASKS (ID, STATE, SEQ)
VALUES (IDX, 'NEW', TASKSEQ.NEXTVAL);
END LOOP;
END;
我现在正在尝试选择在其最新版本中标记为 NEW 的任务。我并不真正关心我处理这些任务的顺序,只关心这些任务在其各自的最新版本中标记为NEW。我想先阅读“旧”任务以避免活锁。我正在获取给定批量大小的任务块。
select 语句看起来像这样:
SELECT L.ID, L.SEQ
FROM TASKS L
INNER JOIN (
SELECT ID, MAX(SEQ) MAXSEQ
FROM TASKS
GROUP BY ID
) R
ON L.ID = R.ID
AND L.SEQ = R.MAXSEQ
WHERE L.STATE = 'NEW'
ORDER BY L.SEQ
FETCH FIRST 100 ROWS ONLY;
一旦任务到达应用程序,它们就会通过以下方式在数据库中进行处理和更新:
UPDATE TASKS
SET STATE = 'DONE'
WHERE ID = ?
AND SEQ = ?;
此更新完成后,将轮询下一批任务。在处理任务时可能已经对表进行了并行写入,但除了上述语句之外,从未从表中删除任何任务。
表格中的数据例如是:
ID|STATE|SEQ
A |NEW |1
A |DONE |2
B |DONE |3
B |NEW |4
C |NEW |5
C |NEW |6
在这种情况下,我希望轮询将包含 (B,4) 和 (C,6) 但不包含 A。将这些元组状态更新为 DONE 后,我希望除非在表中插入更多数据,否则后续轮询将不包含任何数据。
我想知道这个表设计是否可以通过索引有效地实现,以及这个索引会是什么样子。一个简单的索引,例如
CREATE UNIQUE INDEX NEW_TASK_INDEX ON TASKS (ID, SEQ, STATE);
对排序约束不起作用,我想知道如何更改或添加索引来实现我的目标。我还想知道物化视图是否是在其上定义索引的更好选择。
更新:至于建议的解决方案,这里是执行语句的查询计划,添加时
CREATE UNIQUE INDEX tasks_idx1 ON tasks (ID ASC, SEQ DESC);
CREATE UNIQUE INDEX tasks_idx2 ON tasks (STATE, SEQ);
我得到以下计划:
对于更改后的选择语句,我得到以下计划,它看起来更有效,但运行速度比上面的选择慢很多:
【问题讨论】:
-
为什么需要子查询?还是您不是要在连接中输入
AND L.SEQ = R.MAXSEQ?此查询将仅返回 state = new 和最大序列号的项目 -
我添加它以过滤任务列表以仅包含每个任务的最高序列号的任务。例如,如果表包含 id "A" 的序列为 1,2,3 和 "B" 的序列为 4,5,我只想要 A,3 和 B,5。
-
之前和之后的数据真的很有帮助。我无法弄清楚你对数据做了什么。新任务即将到来。任务的旧数据会发生什么变化?如果新任务中有重复项怎么办?这是一次性加载还是新任务一直在不断增加?
-
我用一些示例数据扩展了这个问题。
-
只在 ID 和 SEQ 上创建一个索引,看看是否有帮助