【问题标题】:Selecting the most optimal query选择最优查询
【发布时间】:2013-04-24 05:17:43
【问题描述】:

例如,我在 Oracle 数据库中有一个名为 my_table 的表。它是日志表的类型。它有一个 incremental 列,名为“id”和“registration_number”,对于注册用户唯一。现在我想获得注册用户的最新更改,所以我在下面写了查询来完成这个任务:

第一版:

SELECT t.*
FROM my_table t
WHERE t.id =
  (SELECT MAX(id) FROM my_table t_m WHERE t_m.registration_number = t.registration_number
  );

第二版:

SELECT t.*
FROM my_table t
INNER JOIN
  ( SELECT MAX(id) m_id FROM my_table GROUP BY registration_number
  ) t_m
ON t.id = t_m.m_id;

我的第一个问题是推荐以上查询中的哪一个,为什么?第二个问题是,如果有时该表有大约 70.000 条插入,但插入的行数大多在 0 到 2000 之间变化,是否可以向该表添加索引?

【问题讨论】:

    标签: sql oracle indexing


    【解决方案1】:

    分析查询可能是为每个注册用户获取最新更改的最快方法:

    SELECT registration_number, id
    FROM (
      SELECT
        registration_number,
        id,
        ROW_NUMBER() OVER (PARTITION BY registration_number ORDER BY id DESC) AS IDRankByUser
      FROM my_table
    )
    WHERE IDRankByUser = 1
    

    至于索引,我假设您已经有了registration_number 的索引。 id 上的附加索引将有助于查询,但可能作用不大,也可能不足以证明索引的合理性。我这么说是因为如果您一次插入 70K 行,附加索引会减慢INSERT 的速度。您必须进行试验(并检查执行计划)以确定索引是否值得。

    【讨论】:

    • 感谢您的回答。实际上,起初我是使用 ROW_NUMBER() 来做的,但后来我认为这不是最好的方法,所以我尝试了其他方法来做到这一点。为什么认为它可能会更快?
    • 我已经测试过了,但速度较慢。感谢您在索引方面的时间和建议
    • 我可以想到一个场景,max 查询会更快——如果 oracle 在索引上使用min/max scan。但是你在那里有一个小组,我不确定 oracle 是否可以对复合索引执行最小/最大扫描。值得一试。
    【解决方案2】:

    为了检查更快的查询,您应该检查执行计划和成本,它会给您一个公平的想法。但我同意 Ed Gibbs 的解决方案,因为分析使查询运行得更快。 如果你觉得这个表会变得非常大,那么我会建议对表进行分区并使用本地索引。它们肯定会帮助您形成更快的查询。

    如果您想要插入大量行,那么索引会减慢插入速度,因为每个插入索引也必须更新[我不建议在 ID 上使用索引]。为此我想到了两种解决方案:

    1. 您可以在插入前删除索引,然后在插入后重新创建它。
    2. 使用反向键索引。检查此链接:http://oracletoday.blogspot.in/2006/09/there-is-option-to-create-index.html。反向键索引可能会稍微影响您的查询,因此需要权衡取舍。

    【讨论】:

    • 感谢您对索引的建议,我将尝试实施。但是,不幸的是,使用 ROW_NUMBER() 的查询是其中最慢的一个
    【解决方案3】:

    如果您寻找更快的解决方案并且确实需要维护每个用户的最后活动列表,那么最强大的解决方案是维护单独的表,其中包含唯一的 registration_number 值和 rowid 在日志中创建的最后一条记录表。

    例如(仅用于演示,不检查语法有效性,省略序列和触发器):

    create table my_log(id number not null, registration_number number, action_id varchar2(100))
    /
    
    create table last_user_action(refgistration_number number not null, last_action rowid)
    /
    
    alter table last_user_action 
      add constraint pk_last_user_action primary key (registration_number) using index 
    /
    
    create or replace procedure write_log(p_reg_num number, p_action_id varchar2)
    is
      v_row_id rowid;
    begin
    
      insert into my_log(registration_number, action_id) 
      values(p_reg_num, p_action_id)
      returning rowid into v_row_id;
    
      update last_user_action 
      set last_action = v_row_id 
      where registration_number = p_reg_num;
    
    end;
    /
    

    使用这样的架构,您可以简单地查询每个用户的最后操作,并具有良好的性能:

    select 
    from
      last_user_action lua,
      my_log           l
    where
      l.rowid (+) = lua.last_action
    

    Rowid 是直接寻址存储块的物理存储标识,在移动到另一台服务器、从备份恢复等后,您将无法使用它。但如果您需要此类功能,只需将 my_log 表中的 id 列添加到last_user_action 也是,并根据要求使用一个或另一个。

    【讨论】:

      猜你喜欢
      • 2011-04-11
      • 1970-01-01
      • 1970-01-01
      • 2016-01-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-07-20
      相关资源
      最近更新 更多