как сделать выбор случайных строк в oracle быстрее с таблицей с миллионами строк - PullRequest
14 голосов
/ 30 июня 2010

Есть ли способ ускорить выделение случайных строк в Oracle с помощью таблицы, содержащей миллион строк?Я попытался использовать sample (x) и dbms_random.value, и его запуск занял много времени.

Спасибо!

Ответы [ 7 ]

10 голосов
/ 30 июня 2010

Использование соответствующих значений 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 голосов
/ 01 июля 2010

Сначала начните с ответа Адама, но если SAMPLE недостаточно быстро, даже с оптимизацией ROWNUM, вы можете использовать выборки блоков:

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

Это применяет выборку на уровне блоковвместо каждого ряда.Это означает, что он может пропускать большие объемы данных из таблицы, поэтому процент выборки будет очень грубым.Нет ничего необычного в том, что SAMPLE BLOCK с низким процентом возвращает ноль строк.

2 голосов
/ 12 августа 2010

Вот такой же вопрос на 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
1 голос
/ 27 августа 2013

Кто-то сказал, что sample (x) - самый быстрый способ, которым вы можете.Но для меня этот метод работает немного быстрее, чем метод sample (x).Это должно занять долю секунды (0,2 в моем случае) независимо от размера таблицы.Если это займет больше времени, попробуйте использовать подсказки (- + ведущий (e) use_nl (et) 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))
1 голос
/ 03 июля 2013

Ниже решение этого вопроса не является точным ответом, но во многих сценариях вы пытаетесь выбрать строку и использовать ее для каких-то целей, а затем обновить ее статус с помощью «используется» или «сделано», чтобы вы не выбралиэто снова.

Решение:

Приведенный ниже запрос полезен, но в этом случае, если ваша таблица велика, я только что попробовал и увидел, что вы определенно столкнулись с проблемой производительности в этом запросе.FROM (ВЫБРАТЬ * ОТ таблицы ORDER BY dbms_random.value) WHERE rownum = 1

Таким образом, если вы установите значение rownum, как показано ниже, вы можете обойти проблему производительности.Увеличивая rownum, вы можете уменьшить возможности.Но в этом случае вы всегда будете получать строки из одной и той же 1000 строк.Если вы получаете строку из 1000 и обновляете ее статус с помощью «ИСПОЛЬЗОВАННЫХ», вы почти будете получать разные строки при каждом запросе с помощью «АКТИВНЫХ»

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

обновлять статус строк после его выбора, если вы не можетеобновление означает, что другая транзакция уже использовала его.Затем Вам следует попытаться получить новую строку и обновить ее статус.Кстати, получение одной и той же строки двумя разными транзакциями возможно с 0,001, поскольку rownum - 1000.

0 голосов
/ 18 мая 2016

Можно ли использовать псевдослучайные строки?

select * from (
  select * from ... where... order by ora_hash(rowid)
) where rownum<100
0 голосов
/ 28 августа 2013

Версия с повторными попытками, когда строки не возвращены:

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)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...