【问题标题】:Slow performance selecting next message from custom queue从自定义队列中选择下一条消息的性能缓慢
【发布时间】:2010-12-28 03:26:31
【问题描述】:

我有一个简单的基于表格的队列系统。在最简单的形式中,它由一个 id、一个队列名称和一个状态组成。从给定队列中读取下一条消息时,我们需要确保 FIFO(先进先出),即具有给定状态的给定队列中的最低 id。这一切都适用于几千行,但当我们达到 1M+ 行时,它就不再顺利了。

我们不能使用 rownum = 1,因为这是在排序之前完成的,排序仅基于 id 列 (asc)。如果我创建一个游标并按 id 排序 1000 次,这总共需要大约 100 毫秒,这是一个很好的性能(0.1 毫秒/循环)。如果我在查询中包含状态和队列名称(我需要,因为我需要特定队列的未读消息的最低 id),10 次循环(130 毫秒/循环)大约需要 1300 毫秒,这远非正常。

我已经尝试在三列中的每一列上都有一个索引,还有一个关于 id、队列、状态的组合索引,最后是一个与 id 上的索引的组合,以及一个关于队列和状态的组合索引。 id 列也是主键。所有组合都已在基于规则的设置中尝试过(使用规则提示)。

最好的问候, Michael Ringholm Sundgaard - iHedge A/S www.ihedge.dk www.ibrain.dk

【问题讨论】:

  • 你能告诉我们查询和解释计划吗?

标签: sql oracle sorting queue indexing


【解决方案1】:

我在您尝试的索引中没有看到的一件事是(队列、状态、id)上的索引。如果您将 id 放在索引的开头,它主要会破坏索引的使用,因为您正在寻找“最低的”,在应用其他标准之前这是没有意义的。

索引中列的顺序通常与实际列本身一样重要。

【讨论】:

  • 即使在查询完成和表锁定后进行必要的更新,现在每个循环的执行时间为 0.45 毫秒(半毫秒),非常好,谢谢大家。
  • 复合索引中似乎经常忽略列顺序。
【解决方案2】:

大体思路是:

select id from
(select id
   from queue_table
   where queue_name = 'nameOfQueue'
   and processed = 'NO'
   order by id
)
where rownum = 1

您是否考虑过为此使用 Oracle AQ 而不是自己动手?

【讨论】:

  • 是的。是的,我们也在我们的敏捷 ESB iBrain 中支持 AQ,但我们需要一个更简单、更快的 AQ 版本。从这个意义上说,它还需要是通用的,我们可以在所有主要数据库上实现我们的队列系统 IQ,并使它们的行为相似,我们已经做到了。
【解决方案3】:

我猜你的索引没有被使用,因为还没有为索引收集统计信息。

查看this SO question。您可以在查询中提供提示以强制使用您创建的索引。如果这有帮助,那么为您的表运行 DBMS_STATS.gather_table_stats 包应该强制更新统计信息,从而消除对提示的需要。最终数据库将gather the stats on it's own (see Justin Cave's answer).

【讨论】:

    【解决方案4】:

    您尚未向我们分享查询。与对 1M 行进行排序相比,对数千行进行排序很容易。您需要检查性能可能还有很多其他原因?检查以下内容:

    • 您的表格是否已分析?是否使用了DBMS_STATS.gather_table_statsgather_index_stats
    • 您检查过解释计划吗?它们是否显示使用的索引?
    • 您的 Oracle 是什么版本?

    您应该按照建议尝试Oracle Advanced Queuing

    【讨论】:

    • -- 创建表创建表 IBRAIN_QUEUE_FARM(QUEUE_NAME VARCHAR2(50) 不为空,MESSAGE_ID NUMBER 不为空,STATUS VARCHAR2(20) 不为空,LAST_ACTION_TIME DATE 不为空,MESSAGE_DATA NCLOB 不为空,CORRELATION_ID VARCHAR2( 200) ) 更改表 IBRAIN_QUEUE_FARM 添加约束 PK_IQF_MESSAGE_ID 主键 (MESSAGE_ID) 在 IBRAIN_QUEUE_FARM (QUEUE_NAME, STATUS, MESSAGE_ID) 上创建索引 IBRAIN_Q_F_INDX01
    • -- 查询 select iqf_outer.message_data, iqf_outer.message_id, iqf_outer.correlation_id from ibrain_queue_farm iqf_outer, (select min(iqf.message_id) message_id from ibrain_queue_farm iqf where iqf.queue_name = p_queue_stat and iqf. = '新' ) iqf_inner 其中 iqf_outer.message_id = iqf_inner.message_id;
    • 您应该编辑原始帖子并在那里添加表格和查询信息以供将来参考。
    【解决方案5】:

    一些丑陋/聪明的黑客可能会奏效或可能只是矫枉过正。

    1) 您可以像这样创建一个不错的基于小函数的索引(语法可能有点不对,目前无法访问 Oracle)。

    CREATE INDEX my_small_queue_index 
    ON queue_table ( decode(is_processed,'YES',null,queue_name)
                    ,decode(is_processed,'YES',null,id)
                   );
    

    然后你可以这样选择:

      SELECT --+ index_asc(q my_small_queue_index)
         decode(is_processed,'YES',null,id) AS id
      FROM queue_table q
      WHERE decode(is_processed,'YES',null,queue_name) = 'some queue name'
        AND rownum = 1;
    

    如果有大量已处理的行且只有少数未处理的行(10^9 对几百行),应该会很好。无论如何,应该不会超过几个。

    2) 如果队列名称固定且数量不多,您可以为每个队列创建一个分区。

    【讨论】:

      【解决方案6】:

      建议使用索引提示(无排序依据),即

      SELECT --+ index_asc(q my_small_queue_index) decode(is_processed,'YES',null,id) AS id FROM queue_table q WHERE decode(is_processed,'YES',null,queue_name) = '一些队列名' AND rownum = 1;

      是一个非常危险的。如果该索引被删除、重命名、设置为不可用,或者优化器选择了快速全扫描,那么您将不会收到任何错误,您仍然会得到 1 行,但不能保证它是正确的 行。使用索引很好 - 但您仍然必须有该 order-by 子句以保证正确的结果。

      【讨论】:

        猜你喜欢
        • 2015-02-01
        • 2020-03-24
        • 2016-09-02
        • 1970-01-01
        • 2011-09-09
        • 2013-09-13
        • 1970-01-01
        • 1970-01-01
        • 2014-02-24
        相关资源
        最近更新 更多