Эффективное разбиение на страницы с базой данных Oracle с использованием QueryDSL - PullRequest
0 голосов
/ 11 июля 2019

У меня есть таблица с большим количеством записей в базе данных Oracle. Предположим, что таблица выглядит так:

+--------+----------+-------+--------+
| Column |    Id    | Value | Active |
+--------+----------+-------+--------+
|        | 1        | 123   | Y      |
|        | 2        | 234   | Y      |
|        | 3        | 12345 | N      |
|        | 4        | 98765 | Y      |
|        | ...      | ...   | ...    |
+--------+----------+-------+--------+

Я бы хотел получать эти записи с пометкой Active, помеченной как 'Y', для обработки несколькими потоками (1 страница на поток).

Для этого я могу выполнить следующий запрос:

SELECT Value FROM MyTable WHERE Active = 'Y' OFFSET 1000 ROWS FETCH NEXT 1000 ROWS ONLY;

Или используйте следующие коды в Java:

QMyTable myTable = QMyTable.myTable;
jpaQueryFactory.select(myTable.value)
.from(myTable)
.where(myTable.active.eq('Y'))
.offset(1000)
.limit(1000)
.fetch();

Однако производительность этого запроса снижается, поскольку база данных должна пройти через все предыдущие записи и выбросить их за пределы диапазона.

Для повышения производительности я могу использовать следующий запрос:

SELECT Value FROM MyTable WHERE Active = 'Y' AND Id > 1000 FETCH NEXT 1000 ROWS ONLY;

Java:

QMyTable myTable = QMyTable.myTable;
jpaQueryFactory.select(myTable.value)
.from(myTable)
.where(myTable.active.eq('Y'))
.where(myTable.Id.gt(1000))
.limit(1000)
.fetch();

Приведенный выше код работает, но производительность все еще ухудшается (первая страница заняла 0,1 секунды, но после записи 3M это заняло 7 секунд!). Я делаю это неправильно? Или какие-то другие способы, которые я могу использовать, чтобы ускорить его?

1 Ответ

0 голосов
/ 11 июля 2019

Несколько лет назад проект в моей компании попробовал именно то, что вы делаете. Не только их попытка не удалась, но и проект не удался.

Вот некоторые из опасностей, с которыми вы можете столкнуться:

  • Данные изменяются во время обработки, и все не обрабатывается.
  • Поскольку у вас нет ORDER BY в вашем SELECT, вы получаете несколько строк дважды, а некоторые - совсем нет.
  • Если у вас нет нужных индексов, каждый запрос выполняет полное сканирование таблицы.
  • Если вы используете индекс, запрос по-прежнему отправляется в таблицу для всех строк, включая нужные вам строки. Вероятно, поэтому при просмотре страниц запрос замедляется.

Я настоятельно рекомендую вам пересмотреть свое общее решение, чтобы выполнить обработку в базе данных, если вы можете. Используйте DBMS_PARALLEL_EXECUTE, если вам нужна обработка "сделай сам" (я думаю, что требуется версия 11.2).

Я все равно постараюсь ответить на ваш вопрос в том виде, в котором его спросили.

Большинство требований к нумерации страниц отличаются от ваших! Они хотят отправить первые несколько страниц в пользовательский интерфейс. Вы хотите разбить все активные строки на страницы. Самый эффективный способ сделать это все сразу. Например:

create table t ( id, active, val) as
with actives(active) as (
  select 'Y' from dual union all
  select null from dual union all
  select null from dual
)
, vals(val) as (
  select level from dual
  connect by level <= 1000
)
select rownum, active, val
from actives, vals,
(select null from dual connect by level <= 1000);

select count(*) from t;

 COUNT(*)
---------
  3000000

Теперь создайте промежуточную таблицу с одной строкой на страницу, указывая диапазон ROWID для каждой страницы.

create table pages(page primary key, start_rowid, end_rowid) as
select * from (
  select * from (
    select rowidtochar(rowid) rid,
    ceil(row_number() over(order by rowid) / 1000) page,
    mod(row_number() over(order by rowid), 1000) is_start
    from t
    where active = 'Y'
  )
  where is_start in (1, 0)
)
pivot(max(rid) "ROWID" for is_start in (1 as "START", 0 as "END"));

Это создало 1000 строк для 1000 страниц за 1 секунду. Теперь посмотрим, сколько активных строк на последней странице.

select /*+ gather_plan_statistics */ count(*) from t t
join pages p
  on t.active = 'Y'
  and p.page = 1000
  and t.rowid between p.start_rowid and p.end_rowid;

  COUNT(*)
----------
      1000

Это заняло менее 1/100 секунды.

Вот план выполнения этого запроса. Обратите внимание на шаг TABLE ACCESS BY ROWID RANGE и небольшое количество доступных буферов.

-------------------------------------------------------------------------------------------
| Id  | Operation                     | Name         | Starts | E-Rows | A-Rows | Buffers |
-------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |              |      1 |        |      1 |      13 |
|   1 |  SORT AGGREGATE               |              |      1 |      1 |      1 |      13 |
|   2 |   NESTED LOOPS                |              |      1 |   2500 |   1000 |      13 |
|   3 |    TABLE ACCESS BY INDEX ROWID| PAGES        |      1 |      1 |      1 |       3 |
|   4 |     INDEX UNIQUE SCAN         | SYS_C0012519 |      1 |      1 |      1 |       2 |
|   5 |    TABLE ACCESS BY ROWID RANGE| T            |      1 |   2500 |   1000 |      10 |
-------------------------------------------------------------------------------------------
...