我在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秒!)。我做错了吗?还是我可以使用其他任何方式来加快速度?
答案 0 :(得分:0)
几年前,我公司的一个项目完全尝试了您的工作。他们的尝试不仅失败,而且项目失败。
以下是您可能会遇到的一些危险:
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"));
这在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 |
-------------------------------------------------------------------------------------------