【问题标题】:Oracle table acting like a queueOracle 表就像一个队列
【发布时间】:2016-02-22 08:30:33
【问题描述】:

我们有一个非常高并发的应用程序,其中一些要处理的键与它们的处理优先级一起不断地写入 Oracle 11g 表中。该表上有一个来自序列的主键(ID 字段)。 KEY 字段有一个 UNIQUE 约束。

ID    KEY        PRIORITY
-------------------------
1     ABC           0
2     XYZ           5
3     AZW           0
...
100   CNS           7

上面的表格以非常高的速度插入,比如每分钟大约一万条记录。我们还有大约 100 个并行消费者,它们不断地汇集上表寻找工作。一个这样的消费者一次只需要一个密钥来处理,但至关重要的是,不能两个有相同的密钥一次发送给多个消费者。处理应该发生在PRIORITY,然后是@ 987654323@下单。

为了满足这一点,消费者最终会调用如下函数:

FUNCTION select_key RETURN VARCHAR2
IS
   v_key VARCHAR2(64) := NULL;

   CURSOR keys IS
   SELECT key
     FROM my_table
    ORDER BY priority, id
      FOR UPDATE SKIP LOCKED;
BEGIN   
    OPEN keys
    LOOP
        FETCH keys INTO v_key;
        EXIT WHEN keys%NOTFOUND;
        DELETE FROM my_table WHERE key = v_key;
        EXIT WHEN SQL%ROWCOUNT > 0;
    END LOOP;
    CLOSE keys;

    RETURN v_key;
END;

由于该表的插入和删除率如此之高,该表上的统计信息很快就变得陈旧了。上述 SELECT 的执行计划显示了全表扫描。这会导致密钥选择过程花费的时间越来越长,并且性能会随着时间的推移而显着下降。

除此之外,因为 ORACLE 锁定在数据块级别而不是记录级别,我们遇到的处理并未真正发生在 PRIORITY 后跟 ID 的顺序中。这对我们来说并不是什么大问题,但仍然是我们希望避免的。

这种方法的另一个更大的问题是,您每次都针对一个表运行完整的 SQL,而该表可以轻松获取几万条记录,只是为了获取一个键。

我想到的第一个想法是为此使用一个真正的队列,并让我的并发消费者从中服务。然而,我最终在同步我的表格和队列提要时遇到了各种各样的问题,最终我放弃了这个想法。

任何关于我应该如何更好地解决这个问题的建议将不胜感激。

提前谢谢你。

【问题讨论】:

  • 使用 oracle 减少地图 :)
  • 能否用 Java 重写队列,让消费者转而询问 Java 代码?
  • @Peeyush:通读那个 atm @Thorbjørn Ravn Andersen:将队列保留在内存中不是一种选择。不仅因为我们的应用程序是在多个 JVM 中运行的分布式应用程序,而且很难实现跨国行为
  • 所以您的问题本质上是如何让 Oracle 正常运行?有一个网站!
  • 使用 Oracle AQ 有什么问题?

标签: java oracle performance mq


【解决方案1】:

在 (PRIORITY, ID) 上创建索引,然后查询可以使用INDEX FULL SCAN 按顺序读取数据,而不是扫描整个表。

示例表和数据

drop table my_table;

create table my_table
(
    key varchar2(100) not null,
    id number not null,
    priority number not null,
    constraint my_table_pk primary key (key)
);

insert into my_table
select level, level, level
from dual connect by level <= 100000;

begin
    dbms_stats.gather_table_stats(user, 'MY_TABLE');
end;
/

全表扫描的常规解释计划

explain plan for
select key
from my_table
order by priority, id;

select * from table(dbms_xplan.display);


Plan hash value: 3656711297

---------------------------------------------------------------------------------------
| Id  | Operation          | Name     | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |          |   100K|  1562K|       |   637   (1)| 00:00:01 |
|   1 |  SORT ORDER BY     |          |   100K|  1562K|  2760K|   637   (1)| 00:00:01 |
|   2 |   TABLE ACCESS FULL| MY_TABLE |   100K|  1562K|       |   103   (1)| 00:00:01 |
---------------------------------------------------------------------------------------

为更好的索引访问计划创建索引

成本一开始看起来并不好。但真正的版本应该快得多,因为它会很快停止处理。

create index my_table_idx on my_table(priority, id);

explain plan for
select key
from my_table
order by priority, id;

select * from table(dbms_xplan.display);

Plan hash value: 2209255802

--------------------------------------------------------------------------------------------
| Id  | Operation                   | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |              |   100K|  1562K|   577   (1)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| MY_TABLE     |   100K|  1562K|   577   (1)| 00:00:01 |
|   2 |   INDEX FULL SCAN           | MY_TABLE_IDX |   100K|       |   292   (1)| 00:00:01 |
--------------------------------------------------------------------------------------------

【讨论】:

  • 因为 ID 是主键,并且由于唯一键约束,我的 SELECT 中涉及的列上已经有两个索引,所以不确定在 ID 和 Key 上添加第三个索引是否会有帮助.由于要更新的​​额外索引,插入时间和删除时间更长,甚至可能会变得更糟。请记住,该表以每分钟一万条记录的速度插入和删除。这表示我会试一试。
猜你喜欢
  • 1970-01-01
  • 2011-10-17
  • 1970-01-01
  • 2015-07-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-07
  • 1970-01-01
相关资源
最近更新 更多