最佳实践—如何优化数据全量抽取

本文涉及的产品
云原生数据库 PolarDB 分布式版,标准版 2核8GB
简介: 本文介绍了在应用内通过代码高效抽取数据的方法。

简介

数据抽取是指通过代码或者数据导出工具,从PolarDB-X中批量读取数据的操作。主要包括以下场景:

  • 通过数据导出工具将数据全量抽取到下游系统。PolarDB-X支持多种数据导出工具,更多内容请参考数据导入导出
  • 在应用内处理数据或者批量的将查询结果返回给用户浏览时,不能依赖外部工具,必须在应用内通过代码完成数据全量抽取。

本文主要介绍在应用内通过代码高效抽取数据的方法,根据是否一次性读取全量数据,分为全量抽取和分页查询。

全量抽取场景

全量抽取使用的SQL通常不包含表的拆分键,以全表扫描的方式执行,随着读取数据量的增加,数据抽取操作的执行时间线性增长。为了避免占用过多网络/连接资源,可以使用HINT直接下发查询语句,从物理分片中拉取数据。以下示例采用JAVA代码编写,完整使用方法参考 NODE HINT


public static void extractData(Connection connection, String logicalTableName, Consumer<ResultSet> consumer)
    throws SQLException {
    final String topology = "show topology from {0}";
    final String query = "/*+TDDL:NODE({0})*/select * from {1}";
    try (final Statement statement = connection.createStatement()) {
        final Map<String, List<String>> partitionTableMap = new LinkedHashMap<>();
        // Get partition id and physical table name of given logical table
        try (final ResultSet rs = statement.executeQuery(MessageFormat.format(topology, logicalTableName))) {
            while (rs.next()) {
                partitionTableMap.computeIfAbsent(rs.getString(2), (k) -> new ArrayList<>()).add(rs.getString(3));
            }
        }
        // Serially extract data from each partition
        for (Map.Entry<String, List<String>> entry : partitionTableMap.entrySet()) {
            for (String tableName : entry.getValue()) {
                try (final ResultSet rs = statement
                    .executeQuery(MessageFormat.format(query, entry.getKey(), tableName))) {
                    // Consume data
                    consumer.accept(rs);
                }
            }
        }
    }
}

分页查询场景

向用户展示列表信息时,需要分页来提高页面的加载效率,避免返回过多冗余信息,用于处理分页显示需求的查询,称为分页查询。关系型数据库没有直接提供分段返回表中数据的能力,高效的实现分页查询,还需要结合数据库本身的特点来设计查询语句。

以MySQL为例,分页查询最直观的实现方法,是使用limit offset,pageSize来实现,例如如下查询:


select * from t_order where user_id = xxx order by gmt_create, id limit offset, pageSize

因为gmt_create可能重复,所以order by时应加上id,保证结果顺序的确定性。


说明 该方案在表规模较小的时候,能够正常运行。当t_order表增长到十万级,随着页数增加,执行速度明显变慢,可能降到几十毫秒的量级,如果数据量增长到百万级,则耗时达到秒级,数据量继续增长,耗时最终会变得不可接受。

问题分析

假设我们在user_id, gmt_create上创建了局部索引,由于只有user_id上的条件,每次需要扫描的总数据量为offset + pageSize ,随着offset的增大逐渐接近全表扫描,导致耗时增加。并且在分布式数据库中,全表排序的吞吐无法通过增加DN数量来提高。

改进方案1

每次获取下一页记录时,指定从上次结束的位置继续往后取,这样不需要设置offset ,能够避免出现全表扫描的情况。看一个按id进行分页查询的例子:


select * from t_order where id > lastMaxId order by id limit pageSize

第一次查询不指定条件,后续查询则传入前一次查询的最大id,在执行时,数据库首先在索引上定位到lastMaxId的位置,然后连续返回pageSize条记录即可,非常高效。


说明 当id为主键或者唯一键时,改进方案1可以达到分页查询的效果,也有不错的性能。但缺点也比较明显,当id上有重复值时,可能会漏掉部分记录。

改进方案2

MySQL支持通过 Row Constructor Expression实现多列比较的语义(PolarDB-X同样支持)。


(c2,c3) > (1,1)

等价于
c2 > 1 OR ((c2 = 1) AND (c3 > 1))

因此,可以用下面的方法实现分页查询语义:


select * from t_order 
where user_id = xxx and (gmt_create, id) > (lastMaxGmtCreate, lastMaxId)
order by user_id, gmt_create, id limit pageSize

第一次查询不指定条件,后续查询则传入前一次查询的最大gmt_create和id,通过Row Constructor Expression正确处理gmt_create存在重复的情况。


说明 示例中,为了提高查询性能,我们在user_id和gmt_create上建立联合索引,并在order by中加入user_id提示优化器可以通过索引来消除排序。由于Row Constructor Expression包含null值会导致表达式求值结果为null,当存在null值时需要使用OR表达式。PolarDB-X目前只在Row Constructor Expression仅包含拆分键时才将其用于分区裁剪,其他场景同样需要使用OR表达式。

结合上述分析,给出一个PolarDB-X上分页查询的最佳实践:


-- lastMaxGmtCreate is not null 
select * from t_order
where user_id = xxx
and (
(gmt_create > lastMaxGmtCreate)
or ((gmt_create = lastMaxGmtCreate) and (id > lastMaxId))
)
order by user_id, gmt_create, id limit pageSize
-- lastMaxGmtCreate is null
select * from t_order
where user_id = xxx
and (
(gmt_create is not null)
or (gmt_create is null and id > lastMaxId)
)
order by user_id, gmt_create, id limit pageSize
相关实践学习
快速体验PolarDB开源数据库
本实验环境已内置PostgreSQL数据库以及PolarDB开源数据库:PolarDB PostgreSQL版和PolarDB分布式版,支持一键拉起使用,方便各位开发者学习使用。
相关文章
|
25天前
业务增量数据入仓以及增全量合并工作
增量数据入库与增量全合并工作
51 1
|
7月前
|
SQL 关系型数据库 数据处理
实时计算 Flink版产品使用合集之作业原本只配置了采集一张表,现在想增加一张表,这张新增的表将会增量采集还是重新全量采集
实时计算Flink版作为一种强大的流处理和批处理统一的计算框架,广泛应用于各种需要实时数据处理和分析的场景。实时计算Flink版通常结合SQL接口、DataStream API、以及与上下游数据源和存储系统的丰富连接器,提供了一套全面的解决方案,以应对各种实时计算需求。其低延迟、高吞吐、容错性强的特点,使其成为众多企业和组织实时数据处理首选的技术平台。以下是实时计算Flink版的一些典型使用合集。
101 0
|
数据采集 SQL 关系型数据库
【笔记】最佳实践—如何优化数据全量抽取
本文介绍了在应用内通过代码高效抽取数据的方法。
128 0
|
Java Windows
史上最全的 IDEA Debug 调试技巧(超详细!建议收藏!)(1)
Debug用来追踪代码的运行流程,通常在程序运行过程中出现异常,启用Debug模式可以分析定位异常发生的位置,以及在运行过程中参数的变化。通常我们也可以启用Debug模式来跟踪代码的运行流程去学习三方框架的源码。
2732 0
史上最全的 IDEA Debug 调试技巧(超详细!建议收藏!)(1)
|
数据采集 SQL 关系型数据库
最佳实践—如何优化数据全量抽取
本文介绍了在应用内通过代码高效抽取数据的方法。
201 0
|
存储 数据采集 消息中间件
日志数据入湖的设计与实践
SLS 的队列功能及上下游生态可以为日志入湖提供端到端的支持,要修高速公路(PaaS/SaaS 数据源),也要去做“村村通”(端、开源软件)。 SLS 入湖支持包括四个部分: ● 可靠的采集能力覆盖 ● 弹性的写入与存储能力 ● 日志 ETL 与入湖准备工作 ● 围绕湖生态的模板支持与一键入湖
791 0
|
监控 前端开发 安全
DataX教程(10)- DataX插件热插拔原理
DataX教程(10)- DataX插件热插拔原理
592 0
DataX教程(10)- DataX插件热插拔原理
|
SQL 分布式计算 关系型数据库
优化(2)MaxCompute 实现增量数据推送(全量比对增量逻辑)
MaxCompute(ODPS2.0) - 试用新的集合操作命令 EXCEPT & 增量识别
2609 1
|
分布式计算 Oracle 关系型数据库
数据上云,应该选择全量抽取还是增量抽取?
数据抽取是指从源数据抽取所需要的数据, 是构建数据中台的第一步。 数据源一般是关系型数据库,近几年,随着移动互联网的蓬勃发展,出现了其他类型的数据源,典型的如网站浏览日期、APP浏览日志、IoT设备日志从技术实现方式来讲,从关系型数据库获取数据,可以细分为全量抽取、增量抽取2种方式,两种方法分别适用于不用的业务场景 增量抽取 时间戳方式用时间戳方式抽取增量数据很常见,业务系统在源表上新增一个时间戳字段,创建、修改表记录时,同时修改时间戳字段的值。
3282 0
数据上云,应该选择全量抽取还是增量抽取?
|
SQL 分布式计算 算法
MaxCompute数据仓库在更新插入、直接加载、全量历史表三大算法中的数据转换实践
2018“MaxCompute开发者交流”钉钉群直播分享,由阿里云数据技术专家彬甫带来以“MaxCompute数据仓库数据转换实践”为题的演讲。本文首先介绍了MaxCompute的数据架构和流程,其次介绍了ETL算法中的三大算法,即更新插入算法、直接加载算法、全量历史表算法,再次介绍了在OLTP系统中怎样处理NULL值,最后对ETL相关知识进行了详细地介绍。
5384 0
下一篇
无影云桌面