Oracle分页策略

2023-12-11

我想从两个时间戳之间的表中获取数百万行,然后对其进行处理。触发单个查询并立即检索所有记录看起来是一个糟糕的策略,因为它可能超出我的 JVM 程序的内存容量。

我读过这篇文章:

http://oracle.readthedocs.io/en/latest/sql/indexes/top-n-pagination.html

因此,我计划以1000为批量进行分页,并提出了以下策略: 比方说Start_Date = X and End_Date = Y

  1. 发起查询,

select * from table where CREATE_TIMESTAMP > X AND CREATE_TIMESTAMP < Y ORDER BY CREATE_TIMESTAMP FETCH NEXT 1000 ROWS ONLY.

  1. 如果我只得到不到 1000 行,那就意味着所有记录都已完成。如果我正好得到 1000 行,那就意味着可能还有更多记录。

  2. set X = CREATE_TIMESTAMP of 1000th record

    select * from table where CREATE_TIMESTAMP > X AND CREATE_TIMESTAMP < Y ORDER BY CREATE_TIMESTAMP FETCH NEXT 1000 ROWS ONLY

重复此操作,直到我获得的记录少于 1000 条。

有人认为这种方法有任何问题吗?


分页模式是为了网站呈现而发明的(与滚动导航相反),并且在那里效果最好。简而言之,实时用户实际上无法一次查看数千/数百万条记录,因此信息被分为短页(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% 快!!!
难以置信 ?你可以自己测试一下。
这个例子说明了选择正确的解决方案(正确的设计模式)来解决问题是多么重要。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Oracle分页策略 的相关文章

  • 使用“AND”表达式构建动态 SQL,而不混淆嵌套条件?

    总的来说 我对 php 和编码相当陌生 我有一系列条件需要测试它们是否已设置 它们是 option1 option2 option3 if isset option1 if isset option2 if isset option3 qu
  • Oracle 存储过程的全局临时表的替代方法

    我已阅读并理解 Oracle 仅使用全局临时表 这与允许 temp 表的 MS SQL 不同 我遇到的情况需要我创建数百个全局临时表 以完成我正在进行的从 MS SQL 到 Oracle 的数据库转换 我想知道除了创建必须在数据库中维护的所
  • Google BigQuery,使用“unnest”函数时丢失了空行

    StandardSQL WITH tableA AS SELECT T001 T002 T003 AS T id 1 5 AS L id UNION ALL SELECT T008 T009 AS T id NULL AS L id SEL
  • 在实体框架 6 中使用 SqlQuery>

    我正在尝试在 EF 6 中执行 SQL 查询 select查询返回两个字符串列 例如select a b 并且可以有任意数量的行 我想将结果映射到字典 但我无法摆脱以下错误 错误 1 无法将类型 System Data Entity Inf
  • 无法访问 Big Query 中类型为 ARRAY> 的字段

    我正在尝试在 BigQuery 上使用标准 SQL 方言 即不是旧版 SQL 运行查询 我的查询是 SELECT date hits referer FROM refresh ga sessions xxxxxx LIMIT 1000 但不
  • 没有 Gem 的 Rails 分页

    我的任务是在不使用 gem 的情况下为 Rails 项目创建 前 10 个 后 10 个 最新 和 最旧 链接 在控制器中 我可以显示表中第一组 最新的 10 项 before action set page only index def
  • 如何使用 Retrofit 2 和 RxJava 处理分页

    我知道如何处理 Retrofit 响应 但在使用 rx java 处理来自 REST API 的分页时遇到问题 背景 我使用的其余 api 为我提供了以下响应 并在标题中提供了下一页的链接 HTTP 200 OK Allow GET HEA
  • MySQL Tinybit(1) 通过视图的列

    我有一个连接 2 个表的视图 其中一个表具有表示布尔值的tinyint 1 类型的列 该表在连接时并不总是有条目 因此当行丢失时视图需要采用 0 false 值 我希望视图公开 TINYINT 1 类型且 NOT NULL 类型的列 因为它
  • 将自动递增值添加到只有一列的表中

    我需要创建一个基本上仅保留索引列表的表 因此 我创建了一个只有一个名为 id 的自动递增列的表 但是 我似乎无法隐式地将自动递增值添加到该表中 我知道通常当您在表中有这样一列 不仅仅是此列 时 您可以执行以下操作 插入表 col1 col2
  • 在 SQL 中如何获得整数的最大值?

    我试图从 MySQL 数据库中找出整数 有符号或无符号 的最大值 有没有办法从数据库本身提取这些信息 是否有我可以使用的内置常量或函数 标准 SQL 或 MySQL 特定的 At http dev mysql com doc refman
  • IN 运算符对 SQL 查询性能的影响有多大?

    我的 SQL 查询需要 9 个小时才能执行 见下文 Select Field1 Field2 From A Where Field3 IN 45 unique values here 当我将此查询拆分为 3 个完全相同的查询 仅每个 IN
  • 如何选择列值不不同的每一行

    我需要运行一个 select 语句 返回列值不不同的所有行 例如 EmailAddress 例如 如果表格如下所示 CustomerName EmailAddress Aaron email protected cdn cgi l emai
  • 在 Oracle 中使用数据透视表的建议

    我需要一份报告 我应该使用数据透视表 报告将按类别分组 使用 case when 语句不好 因为有很多类别 您可以将 Northwind 数据库视为示例 所有类别将显示为列和报告将显示客户在类别中的偏好 我不知道另一个解决方案 并在互联网上
  • SQLite 使用循环重新编号 ID

    您好 我有一个包含许多插入行的表 我需要按 id 对所有行重新编号并排序 我找到了这段代码 但它对我不起作用 SET i 100 UPDATE main Categories SET ID i i 1 WHERE Name White AL
  • 如何在 Postgresql 中将 GIST 或 GIN 索引与 hstore 列一起使用?

    我正在使用 postgresql 9 3 的 hstore 我正在尝试对 hstore 列使用索引就像文档所述 http www postgresql org docs 9 3 static hstore html 我的问题是索引似乎没有被
  • 无法从 Vue 3.2 中的 V-Data-Table 中删除页脚/分页

    所以 我正在使用 VueJs 3 2 Vite 3 2 4 Vuetify 3 2 2 我知道应该如何声明 DataTable 上的选项 以便删除页脚和分页 如下所示
  • Postgres 按查询分组

    我正在尝试在 postgres 的查询中使用 group by 我无法让它按照我想要的方式工作 以便根据需要对结果进行分组 这是另一个堆栈问题的扩展我刚刚回答过的递归查询 https stackoverflow com questions
  • 如何重置 SQL Server 中表的 IDENTITY 列? [复制]

    这个问题在这里已经有答案了 我怎样才能重置我的IDENTITY我已经填充的表中的列 我尝试过类似的方法 但它不起作用 WITH TBL AS SELECT ROW NUMBER OVER ORDER BY profile id AS RN
  • 在 Oracle 中如何将多行组合成逗号分隔的列表? [复制]

    这个问题在这里已经有答案了 我有一个简单的查询 select from countries 结果如下 country name Albania Andorra Antigua 我想在一行中返回结果 如下所示 Albania Andorra
  • 总结同一个 SQL 表上的两个条件

    给定一个 SQL 表 Transactions ID INT COMPANY ID INT STATUS INT where STATUS IN 0 1 表示免费交易并且STATUS IN 2 3 表示可计费交易 简单的 我希望 ANSI

随机推荐