sql - 如何有效地查询具有修订值的表?

标签 sql oracle indexing

我需要存储一个任务项目表,其中每个项目都有一个唯一的标识符。任务可以多次到达,因此标识符不是主键。然而,我只关心我使用序列识别的任务的最新版本。任务的每个实例都可以是NEWDONE。这些表格看起来有点像这样:

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”。我想先阅读“旧”任务以避免活锁。我正在获取给定批量大小的任务 block 。

选择语句看起来像这样:

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);

并没有对排序约束起到作用,我想知道如何更改或添加索引来实现我的目标。我还想知道物化 View 是否是在其上定义索引的更好选择。


更新:至于建议的解决方案,这里是添加时执行语句的查询计划

CREATE UNIQUE INDEX tasks_idx1 ON tasks (ID ASC, SEQ DESC);
CREATE UNIQUE INDEX tasks_idx2 ON tasks (STATE, SEQ); 

我得到以下计划:

Query plan first suggestion

对于更改后的 select 语句,我得到以下计划,该计划似乎更有效,但运行速度比上述选择慢得多:

enter image description here

最佳答案

根据此评论于 2019 年 3 月 22 日更新

Please check whether the query addresses this case from OP "In this case, I would expect that a polling would contain (B,4) and (C,6) but not A"

我会从这个开始:

设置

(与您的相同,但我添加了 TASK_DATA 列以获得更准确的结果)

CREATE SEQUENCE TASKSEQ;

DROP TABLE TASKS;

CREATE TABLE TASKS (
  ID VARCHAR2(100),
  STATE VARCHAR2(50),
  SEQ NUMBER(20),
  TASK_DATA VARCHAR2(500)
);

BEGIN
  FOR IDX IN 1..1000000
    LOOP
      INSERT INTO TASKS (ID, STATE, SEQ, TASK_DATA)
      VALUES (IDX, 'DONE', TASKSEQ.NEXTVAL, LPAD('.',500,'.'));
    END LOOP;
  FOR IDX IN 900001..1000000
    LOOP
      INSERT INTO TASKS (ID, STATE, SEQ, TASK_DATA)
      VALUES (IDX, 'NEW', TASKSEQ.NEXTVAL, LPAD('.',500,'.'));
    END LOOP;
END;

STATE上创建索引, ID , SEQ

CREATE INDEX tasks_n1 ON tasks ( STATE, ID, SEQ );
EXEC DBMS_STATS.GATHER_TABLE_STATS(user,'TASKS');

查询

SELECT l.id, l.seq, l2.task_data FROM
(
SELECT l.rowid row_id, 
       l.id, 
       l.seq, 
       max(l.seq) keep ( dense_rank first order by l.seq desc) 
                  over ( partition by l.id) maxseq
FROM   tasks l
WHERE l.state = 'NEW'
AND NOT EXISTS ( SELECT 'later, completed task for ID'
                 FROM   tasks l3
                 WHERE  l3.id = l.id
                 AND    l3.state = 'DONE'
                 AND    l3.seq > l.seq )
ORDER BY l.seq
) l
INNER JOIN tasks l2 ON l2.rowid = l.row_id
WHERE l.seq = l.maxseq
AND ROWNUM <= 100
;

在我的系统上,该查询运行时获取了 4,433 个缓冲区。这不太好,但如果它运行得足够频繁以至于大部分索引都在缓存中,那么它在大多数系统上可能会在几秒钟内运行。几乎所有的缓冲区获取都在读取索引。

一些注意事项:

1) 我添加了一个 TASK_DATA 列,以避免获得看起来很棒的结果,因为索引覆盖了整个 SELECT 列表和/或每个 block 的行数不切实际,使得完整扫描看起来比实际情况要好真的。

2) 这种方法运行得相对较快,因为索引涵盖了满足 l 所需的所有内容。内联 View ,因此它可以通过仅读取索引来完成该工作。对 l 的 100,000 行进行排序返回的速度相当快且小,通常可以在内存中完成。最后,只费心去表了TASK_DATA您实际想要返回的 100 行的信息。

关于sql - 如何有效地查询具有修订值的表?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55280743/

相关文章:

javascript - 为什么即使索引已关闭,该索引号似乎仍有效?

sql-server - 如何加快重新创建集群索引

sql - 是否可以从同一SQL语句中的多个表中删除?

sql - 从触发器编写大量 DML 的最佳方法

sql - Oracle SQL regexp_replace 在 OR 组处停止

sql - 选择所有出生日期在月底的员工

oracle - 使用sqlplus命令行隐藏明文密码

java - 如何使用 lucene 进行词形还原和消除空法语单词

sql - 如何从 Oracle 中的句子中删除符号?

mysql - 将 mysqlu 中的值更新为各种值