【问题标题】:how to make selecting random rows in oracle faster with table with millions of rows如何使用具有数百万行的表更快地在 oracle 中选择随机行
【发布时间】:2010-06-30 15:05:57
【问题描述】:

有没有一种方法可以在具有数百万行的表中更快地在 oracle 中选择随机行。我尝试使用 sample(x) 和 dbms_random.value 并且它需要很长时间才能运行。

谢谢!

【问题讨论】:

  • 请您正在执行的sql。
  • 列上是否有用于选择行的索引?
  • @Janek:示例仅适用于全表扫描。

标签: oracle


【解决方案1】:

AskTom 上也有同样的问题:

http://asktom.oracle.com/pls/apex/f?p=100:11:0::::P11_QUESTION_ID:6075151195522

如果您知道您的表有多大,请使用上述示例块。如果你不这样做,你可以修改下面的例程来获得你想要的行数。

复制自:http://asktom.oracle.com/pls/apex/f?p=100:11:0::::P11_QUESTION_ID:6075151195522#56174726207861

create or replace function get_random_rowid
( table_name varchar2
) return urowid
as
sql_v varchar2(100);
urowid_t dbms_sql.urowid_table;
cursor_v integer;
status_v integer;
rows_v integer;
begin
  for exp_v in -6..2 loop
    exit when (urowid_t.count > 0);
    if (exp_v < 2) then
      sql_v := 'select rowid from ' || table_name
      || ' sample block (' || power(10, exp_v) || ')';
    else
      sql_v := 'select rowid from ' || table_name;
    end if;
    cursor_v := dbms_sql.open_cursor;
    dbms_sql.parse(cursor_v, sql_v, dbms_sql.native);
    dbms_sql.define_array(cursor_v, 1, urowid_t, 100, 0);
    status_v := dbms_sql.execute(cursor_v);
    loop
      rows_v := dbms_sql.fetch_rows(cursor_v);
      dbms_sql.column_value(cursor_v, 1, urowid_t);
      exit when rows_v != 100;
    end loop;
    dbms_sql.close_cursor(cursor_v);
  end loop;
  if (urowid_t.count > 0) then
    return urowid_t(trunc(dbms_random.value(0, urowid_t.count)));
  end if;
  return null;
exception when others then
  if (dbms_sql.is_open(cursor_v)) then
    dbms_sql.close_cursor(cursor_v);
  end if;
  raise;
end;
/
show errors

【讨论】:

    【解决方案2】:

    使用适当的sample(x) 值是最快的方法。它是块内的块随机和行随机,所以如果你只想要一个随机行:

    select dbms_rowid.rowid_relative_fno(rowid) as fileno,
           dbms_rowid.rowid_block_number(rowid) as blockno,
           dbms_rowid.rowid_row_number(rowid) as offset
      from (select rowid from [my_big_table] sample (.01))
     where rownum = 1
    

    我正在使用一个子分区表,即使抓取多行,我也能获得很好的随机性:

    select dbms_rowid.rowid_relative_fno(rowid) as fileno,
           dbms_rowid.rowid_block_number(rowid) as blockno,
           dbms_rowid.rowid_row_number(rowid) as offset
      from (select rowid from [my_big_table] sample (.01))
     where rownum <= 5
    
        FILENO    BLOCKNO     OFFSET
    ---------- ---------- ----------
           152    2454936         11
           152    2463140         32
           152    2335208          2
           152    2429207         23
           152    2746125         28
    

    我怀疑您可能应该调整您的 SAMPLE 子句以使用适当的样本大小来获取您要获取的内容。

    【讨论】:

      【解决方案3】:

      先从 Adam 的回答开始,但如果 SAMPLE 不够快,即使使用 ROWNUM 优化,您也可以使用块样本:

      ....FROM [table] SAMPLE BLOCK (0.01)
      

      这会在块级别而不是每行应用采样。这确实意味着它可以跳过表中的大量数据,因此样本百分比将非常粗糙。百分比较低的 SAMPLE BLOCK 返回零行并不罕见。

      【讨论】:

        【解决方案4】:

        下面这个问题的解决方案不是确切的答案,但在许多情况下,您尝试选择一行并尝试将其用于某种目的,然后将其状态更新为“已使用”或“完成”,这样您就不会选择再说一遍。

        解决方案:

        下面的查询很有用,但如果您的表很大,我只是尝试过,发现您肯定会遇到此查询的性能问题。

        选择 * 从 ( 选择 * 从表 按 dbms_random.value 排序) WHERE rownum = 1

        因此,如果您设置如下所示的 rownum,则可以解决性能问题。通过增加 rownum 可以减少可能性。但在这种情况下,您将始终从相同的 1000 行中获取行。如果您从 1000 中获取一行并使用“USED”更新其状态,则每次使用“ACTIVE”查询时几乎都会得到不同的行

        SELECT * FROM
        ( SELECT * FROM table
        where rownum < 1000
          and status = 'ACTIVE'
          ORDER BY dbms_random.value  )
        WHERE rownum = 1
        

        选择后更新行状态,如果不能更新则表示另一个事务已经使用它。然后您应该尝试获取新行并更新其状态。顺便说一句,由于 rownum 是 1000,所以两个不同的交易获得同一行的可能性是 0.001。

        【讨论】:

        • 在 stackoverflow 中不鼓励仅链接的答案。此外,Oracle 代码在大表上会非常慢。
        • 刚修不知道
        【解决方案5】:

        有人告诉你 sample(x) 是最快的方法。 但对我来说,这种方法比 sample(x) 方法工作得稍微快一些。 无论表的大小是多少,它都应该占用一秒的一小部分(在我的情况下为 0.2)。如果需要更长的时间尝试使用提示(--+leading(e) use_nl(e t) rowid(t))会有所帮助

        SELECT *
          FROM My_User.My_Table
         WHERE ROWID = (SELECT MAX(t.ROWID) KEEP(DENSE_RANK FIRST ORDER BY dbms_random.value)
                          FROM (SELECT o.Data_Object_Id,
                                       e.Relative_Fno,
                                       e.Block_Id + TRUNC(Dbms_Random.Value(0, e.Blocks)) AS Block_Id
                                  FROM Dba_Extents e
                                  JOIN Dba_Objects o ON o.Owner = e.Owner AND o.Object_Type = e.Segment_Type AND o.Object_Name = e.Segment_Name
                                 WHERE e.Segment_Name = 'MY_TABLE'
                                   AND(e.Segment_Type, e.Owner, e.Extent_Id) =
                                      (SELECT MAX(e.Segment_Type) AS Segment_Type,
                                              MAX(e.Owner)        AS Owner,
                                              MAX(e.Extent_Id) KEEP(DENSE_RANK FIRST ORDER BY Dbms_Random.Value) AS Extent_Id
                                         FROM Dba_Extents e
                                        WHERE e.Segment_Name = 'MY_TABLE'
                                          AND e.Owner = 'MY_USER'
                                          AND e.Segment_Type = 'TABLE')) e
                          JOIN My_User.My_Table t
                            ON t.Rowid BETWEEN Dbms_Rowid.Rowid_Create(1, Data_Object_Id, Relative_Fno, Block_Id, 0)
                           AND Dbms_Rowid.Rowid_Create(1, Data_Object_Id, Relative_Fno, Block_Id, 32767))
        

        【讨论】:

        • 有趣的方法。但我无法让它在我的系统上持续快速运行。对于小表,它通常不返回任何行。
        • 我认为速度不一致,因为 Dba_Extents 缓存或 sql 解析。
        • "对于小表,它通常不返回任何行"
        • “对于小表,它通常不返回任何行”。这是因为范围的最小值为 64k。在我的机器上 8 个数据块。如果表有几行。所有行都在一个块中。所以在这种情况下,获得任何记录的随机块的概率是 1/8。如果 x 接近 1/record_count,则 sample(x) 经常不返回任何行。对于小型表,无需担心 max(rowid) keep(dense_rank first order by dms_random.value) 工作正常。
        • 如果您有表统计信息,您可以获取水印并使用它而不是 Dba_Extens.Blocks 来减少“没有返回行”的情况。此外,扩展也很少有 oracle 保留的块。也可以避免这些障碍
        【解决方案6】:

        没有行返回时重试的版本:

        WITH gen AS ((SELECT --+ inline leading(e) use_nl(e t) rowid(t)
                             MAX(t.ROWID) KEEP(DENSE_RANK FIRST ORDER BY dbms_random.value) Row_Id
                        FROM (SELECT o.Data_Object_Id,
                                     e.Relative_Fno,
                                     e.Block_Id + TRUNC(Dbms_Random.Value(0, e.Blocks)) AS Block_Id 
                                FROM Dba_Extents e
                                JOIN Dba_Objects o ON o.Owner = e.Owner AND o.Object_Type = e.Segment_Type AND o.Object_Name = e.Segment_Name
                               WHERE e.Segment_Name = 'MY_TABLE'
                                 AND(e.Segment_Type, e.Owner, e.Extent_Id) =
                                    (SELECT MAX(e.Segment_Type) AS Segment_Type,
                                            MAX(e.Owner)        AS Owner,
                                            MAX(e.Extent_Id) KEEP(DENSE_RANK FIRST ORDER BY Dbms_Random.Value) AS Extent_Id
                                       FROM Dba_Extents e
                                      WHERE e.Segment_Name = 'MY_TABLE'
                                        AND e.Owner = 'MY_USER'
                                        AND e.Segment_Type = 'TABLE')) e
                        JOIN MY_USER.MY_TABLE t ON t.ROWID BETWEEN Dbms_Rowid.Rowid_Create(1, Data_Object_Id, Relative_Fno, Block_Id, 0)
                                                          AND Dbms_Rowid.Rowid_Create(1, Data_Object_Id, Relative_Fno, Block_Id, 32767))),
          Retries(Cnt, Row_Id) AS (SELECT 1, gen.Row_Id
                                     FROM Dual
                                     LEFT JOIN gen ON 1=1
                                    UNION ALL
                                   SELECT Cnt + 1, gen.Row_Id
                                     FROM Retries
                                     LEFT JOIN gen ON 1=1
                                    WHERE Retries.Row_Id IS NULL AND Retries.Cnt < 10)
        SELECT *
          FROM MY_USER.MY_TABLE
         WHERE ROWID = (SELECT Row_Id
                          FROM Retries
                         WHERE Row_Id IS NOT NULL)
        

        【讨论】:

        • 您可能应该删除此答案并将其添加到您的其他答案中。此外,您可能希望将提示放在原始答案中。值得注意的是,SEGMENT_TYPE 并不总是“TABLE”,它也可能是“TABLE PARTITION”,可能还有其他一些值。
        【解决方案7】:

        你可以使用伪随机行吗?

        select * from (
          select * from ... where... order by ora_hash(rowid)
        ) where rownum<100
        

        【讨论】:

          猜你喜欢
          • 2012-01-16
          • 2011-05-11
          • 1970-01-01
          • 2016-06-19
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-04-15
          • 2011-01-04
          相关资源
          最近更新 更多