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分页策略 的相关文章

  • 使用 TLS 证书 JDBC 连接到 Oracle 数据库

    我正在尝试用 Java 编写一个连接类来使用 JDBC 驱动程序连接到 Oracle 数据库 但我想保护用于连接到 Oracle 数据库的参数 例如 jdbcurl 用户名 密码 我必须使用 TLS 证书概念来连接到 Java 中的 Ora
  • Linq To SQL - 拥有和分组依据

    我下面这个查询工作正常 不过我想使用 Linq 来实现它 select u ID u NAME from Task t join BuildingUser bu ON bu ID BUILDING t ID BUILDING join Us
  • PostgreSQL 中“-”处或附近的语法错误

    我正在尝试运行查询来更新用户密码 alter user dell sys with password Pass 133 但因为 它给了我这样的错误 ERROR syntax error at or near LINE 1 alter use
  • SQL Join 列上类似于另一列[重复]

    这个问题在这里已经有答案了 可能的重复 mysql连接查询使用like https stackoverflow com questions 1930809 mysql join query using like 我想要进行连接 其中一列包含
  • 检查两个“select”是否相等

    有没有办法检查两个 非平凡的 选择是否等效 最初我希望两个选择之间有形式上的等价 但是答案在证明 sql 查询等价性 https stackoverflow com questions 56895 proving sql query equ
  • 如何在 Oracle PLSQL 中提交单个事务

    我需要编写一个 PL SQL 过程 在这个过程中 我需要在其自己的事务范围内调用另一个过程 并提交它 无论主事务是否失败或提交 换句话说 我需要类似的东西REQUIRES NEW交易传播 就像是 procedure mainProcedur
  • 根据日期顺序排名

    我的数据如下 Heading Date A 2009 02 01 B 2009 02 03 c 2009 02 05 d 2009 02 06 e 2009 02 08 我需要如下排名 Heading Date Rank A 2009 02
  • sql查询获取从一月到当月的所有数据,即使没有记录

    我不擅长 sql 所以任何帮助世界都很棒 我有一个 SQL 查询 可以获取从一月到当月注册的记录 我的代码示例 SELECT DatePart YEAR p createStamp as TheYear DatePart MONTH p c
  • JDBC插入实数数组

    我试图将一个真实的数组插入到 postgresql 数组中 该表的定义是 String sqlTable CREATE TABLE IF NOT EXISTS ccmBlock sampleId INTEGER block REAL 插入内
  • 快速查询最新记录的方法?

    我有一张这样的表 USER PLAN START DATE END DATE 1 A 20110101 NULL 1 B 20100101 20101231 2 A 20100101 20100505 在某种程度上 如果END DATE i
  • st_intersects 与 st_overlaps

    这两个查询有什么区别 select a gid sum length b the geom from polygons as a roads as b where st intersects a the geom b the geom gr
  • H2 用户定义的聚合函数 ListAgg 不能在第一个参数上使用 DISTINCT 或 TRIM()

    所以我有一个 DB2 生产数据库 我需要在其中使用可用的函数 ListAgg 我希望使用 H2 的单元测试能够正确测试此功能 不幸的是H2不直接支持ListAgg 但是 我可以创建一个用户定义的聚合函数 import java sql Co
  • 内置函数将每个单词的第一个字母大写

    如果 SQL Server 中已存在此类函数 我不想为此创建自定义函数 输入字符串 This is my string to convert预期输出 This Is My String To Convert SET ANSI NULLS O
  • 如何在 DB2 中创建返回序列值的函数?

    如何在 DB2 中创建一个从序列中获取值并返回该值的函数 应该可以在 select 或 insert 语句中使用该函数 例如 select my func from xxx insert into xxx values my func 基本
  • 从 Getdate() 获取时间

    我想采取Getdate 结果 例如 2011 10 05 11 26 55 000 into 11 26 55 AM 我看过其他地方并发现 Select RIGHT CONVERT VARCHAR GETDATE 100 7 这给了我 11
  • SELECT max(x) 返回 null;我怎样才能让它返回0?

    运行以下命令时如何返回 0 而不是 null SELECT MAX X AS MaxX FROM tbl WHERE XID 1 假设没有XID 1的行 or SELECT coalesce MAX X 0 AS MaxX FROM tbl
  • MYSQL从每个类别中随机选择一条记录

    我有一个数据库Items表看起来像这样 id name category int 有几十万条记录 每个item可以是 7 种不同的之一categories 对应于categories table id category 我想要一个从每个类别
  • 如何获取自定义订单的结果? [关闭]

    Closed 这个问题需要细节或清晰度 help closed questions 目前不接受答案 代替ASC or DESC 我希望我的查询结果采用特定的自定义顺序 例如 如果我想要的结果不是 A B C D 而是 P A L H 该怎么
  • Oracle Data Provider for .NET 不支持 Oracle 19.0.48.0.0

    我们刚刚升级到 Oracle 19c 19 3 0 所有应用程序都停止工作并出现以下错误消息 Oracle Data Provider for NET 不支持 Oracle 19 0 48 0 0 我将 Oracle ManagedData
  • 是否可以引用同一个表中的不同列?

    如果博客有一个 类别 表 如下所示 CREATE TABLE categories id INTEGER PRIMARY KEY AUTO INCREMENT parent id INTEGER NOT NULL name VARCHAR

随机推荐