【问题标题】:Efficient pagination with Oracle database using QueryDSL使用 QueryDSL 对 Oracle 数据库进行高效分页
【发布时间】:2019-11-20 19:49:51
【问题描述】:

我在 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 秒!)。我做错了吗?或者我可以使用其他任何方式来加快速度?

【问题讨论】:

标签: java database oracle querydsl


【解决方案1】:

几年前,我公司的一个项目完全尝试了您正在做的事情。他们的尝试不仅失败了,而且项目也失败了。

以下是您可能遇到的一些危险:

  • 在您进行处理时数据会发生变化,并且不会处理所有内容。
  • 由于您的SELECT 中没有ORDER BY,因此有些行会出现两次,有些则根本没有。
  • 如果您没有正确的索引,每个查询都会对表进行全面扫描。
  • 如果您确实使用了索引,查询仍会转到表中的所有行,包括您想要的行。这可能就是您浏览页面时查询速度变慢的原因。

我强烈建议你重新考虑你的整体解决方案,尽可能在数据库中进行处理。如果您需要“自己动手”处理(我认为需要 11.2 版),请使用 DBMS_PARALLEL_EXECUTE。

我仍然会按照你的要求回答你的问题。

大多数分页要求与您的不同!他们希望将前几页发送到用户界面。您想将所有活动行分解为页面。最有效的方法是一次性完成。例如:

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"));

这在 1 秒内为 1000 个页面创建了 1000 行。现在让我们看看最后一页有多少活动行。

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 |
-------------------------------------------------------------------------------------------

【讨论】:

    猜你喜欢
    • 2011-10-14
    • 2018-08-24
    • 1970-01-01
    • 2010-09-19
    • 1970-01-01
    • 2018-10-22
    • 2019-03-23
    • 2012-07-25
    • 2021-06-09
    相关资源
    最近更新 更多