分页模式是为了网站呈现而发明的(与滚动导航相反),并且在那里效果最好。简而言之,实时用户实际上无法一次查看数千/数百万条记录,因此信息被分为短页(50~200 条记录),其中每页通常向数据库发送一个查询。用户通常只点击几个页面,但不会浏览全部页面,另外用户需要一点时间来浏览页面,因此查询不是逐一发送到数据库的,而是以很长的间隔发送。检索一大块数据的时间比检索所有数百万条记录要短得多,因此用户很高兴,因为他不必等待后续页面很长时间,并且整体系统负载更小。
但从这个问题看来,你的应用程序的性质是面向批量处理而不是到网络演示。应用程序必须获取所有记录并对每条记录执行一些操作/转换(计算)。在这种情况下,使用完全不同的设计模式(流/管道处理、步骤顺序、并行步骤/操作等),如果你这样做,分页将不起作用你会毁掉你的系统性能。
让我们看一下简单实用的示例,而不是花哨的理论,它将向您展示我们在这里讨论的速度差异
假设有一张桌子PAGINATION
大约有 700 万条记录:
create table pagination as
select sysdate - 200 * dbms_random.value As my_date, t.*
from (
select o.* from all_objects o
cross join (select * from dual connect by level <= 100)
fetch first 10000000 rows only
) t;
select count(*) from pagination;
COUNT(*)
----------
7369600
假设有一个索引创建于MY_DATE
列和索引统计信息是新鲜的:
create index PAGINATION_IX on pagination( my_date );
BEGIN dbms_stats.gather_table_stats( 'TEST', 'PAGINATION', method_opt => 'FOR ALL COLUMNS' ); END;
/
假设我们将在以下日期之间处理表中大约 10% 的记录:
select count(*) from pagination
where my_date between date '2017-10-01' and '2017-10-21';
COUNT(*)
----------
736341
最后,为了简单起见,我们的“处理”将包括对字段之一的长度进行简单求和。
这是一个简单的分页实现:
public class Pagination {
public static class RecordPojo {
Date myDate;
String objectName;
public Date getMyDate() {
return myDate;
}
public RecordPojo setMyDate(Date myDate) {
this.myDate = myDate;
return this;
}
public String getObjectName() {
return objectName;
}
public RecordPojo setObjectName(String objectName) {
this.objectName = objectName;
return this;
}
};
static class MyPaginator{
private Connection conn;
private int pageSize;
private int currentPage = 0;
public MyPaginator( Connection conn, int pageSize ) {
this.conn = conn;
this.pageSize = pageSize;
}
static final String QUERY = ""
+ "SELECT my_date, object_name FROM pagination "
+ "WHERE my_date between date '2017-10-01' and '2017-10-21' "
+ "ORDER BY my_date "
+ "OFFSET ? ROWS FETCH NEXT ? ROWS ONLY";
List<RecordPojo> getNextPage() {
List<RecordPojo> list = new ArrayList<>();
ResultSet rs = null;
try( PreparedStatement ps = conn.prepareStatement(QUERY);) {
ps.setInt(1, pageSize * currentPage++ );
ps.setInt(2, pageSize);
rs = ps.executeQuery();
while( rs.next()) {
list.add( new RecordPojo().setMyDate(rs.getDate(1)).setObjectName(rs.getString(2)));
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
try{rs.close();}catch(Exception e) {}
}
return list;
}
public int getCurrentPage() {
return currentPage;
}
}
public static void main(String ...x) throws SQLException {
OracleDataSource ds = new OracleDataSource();
ds.setURL("jdbc:oracle:thin:test/test@//localhost:1521/orcl");
long startTime = System.currentTimeMillis();
long value = 0;
int pageSize = 1000;
try( Connection conn = ds.getConnection();){
MyPaginator p = new MyPaginator(conn, pageSize);
List<RecordPojo> list;
while( ( list = p.getNextPage()).size() > 0 ) {
value += list.stream().map( y -> y.getObjectName().length()).mapToLong(Integer::longValue).sum();
System.out.println("Page: " + p.getCurrentPage());
}
System.out.format("==================\nValue = %d, Pages = %d, time = %d seconds", value, p.getCurrentPage(), (System.currentTimeMillis() - startTime)/1000);
}
}
}
结果是:
Value = 18312338, Pages = 738, time = 2216 seconds
现在让我们测试一个非常简单的基于流的解决方案 - 只需仅获取一条记录,处理它,丢弃它(释放内存),然后获取下一条记录。
public class NoPagination {
static final String QUERY = ""
+ "SELECT my_date, object_name FROM pagination "
+ "WHERE my_date between date '2017-10-01' and '2017-10-21' "
+ "ORDER BY my_date ";
public static void main(String[] args) throws SQLException {
OracleDataSource ds = new OracleDataSource();
ds.setURL("jdbc:oracle:thin:test/test@//localhost:1521/orcl");
long startTime = System.currentTimeMillis();
long count = 0;
ResultSet rs = null;
PreparedStatement ps = null;
try( Connection conn = ds.getConnection();){
ps = conn.prepareStatement(QUERY);
rs = ps.executeQuery();
while( rs.next()) {
// processing
RecordPojo r = new RecordPojo().setMyDate(rs.getDate(1)).setObjectName(rs.getString(2));
count+=r.getObjectName().length();
}
System.out.format("==================\nValue = %d, time = %d seconds", count, (System.currentTimeMillis() - startTime)/1000);
}finally {
try { rs.close();}catch(Exception e) {}
try { ps.close();}catch(Exception e) {}
}
}
结果是:
Value = 18312328, time = 11 seconds
是 - 2216 秒 / 11 秒 = 快 201 倍 -20 100% 快!!!
难以置信 ?你可以自己测试一下。
这个例子说明了选择正确的解决方案(正确的设计模式)来解决问题是多么重要。