3. 数据库采集异常排查:CDC延迟、连接池耗尽、全量同步OOM问题

数据库采集,说白了就是数据仓库的「水源地」。水源出了问题,下游整个数据管道都得跟着遭殃。我这些年排查过的采集异常,少说也有上百个了。今天咱们就挑三个最要命的场景聊聊——CDC延迟、连接池耗尽、全量同步OOM。

这三个问题,几乎每个做数据采集的团队都会遇到。你想想看,CDC延迟了,实时报表就变「昨日报表」;连接池耗尽了,整个采集任务直接挂掉;全量同步OOM了,服务器都可能被拖垮。嗯,咱们一个一个拆解。

核心观点:数据库采集异常,80% 的根因都在「资源竞争」和「数据倾斜」上。排查思路要遵循「从外到内、从宏观到微观」的原则。

3.1 CDC延迟:不只是网络慢那么简单

CDC(Change Data Capture)延迟,是实时数仓最头疼的问题之一。我记得有一次,业务方反馈说「实时大屏的数据比实际晚了20分钟」。我第一反应是网络问题,结果一查,网络延迟只有2ms。

那问题出在哪?

我个人的排查习惯是这样的:

  1. 先看源库的日志生成速度——MySQL的binlog、Oracle的redo log,生成速度是否正常?
  2. 再看CDC工具的消费能力——Canal、Debezium、Maxwell这些工具,有没有出现「积压」?
  3. 最后看目标端的写入瓶颈——Kafka、HDFS、或者目标数据库,写入速度是否跟得上?

这里有个坑,我曾经踩过:大事务导致的CDC延迟。比如一个UPDATE语句更新了1000万行数据,binlog里会生成1000万条记录。CDC工具要一条一条解析,速度自然就慢了。

避坑指南:我曾经遇到过一个案例,业务方每天凌晨跑批量任务,导致CDC延迟飙升到30分钟。解决方案很简单——把大事务拆成小批次,或者对CDC任务做「限流+告警」。

排查CDC延迟,我建议用这个流程:

# 1. 检查CDC工具的状态(以Canal为例)
canal-admin> show running
# 查看延迟时间(单位:秒)
canal-admin> show delay

# 2. 检查Kafka消费端积压
kafka-consumer-groups --bootstrap-server localhost:9092 \
  --group canal_group --describe

# 3. 检查目标数据库写入速度
# MySQL写入速度监控
SHOW GLOBAL STATUS LIKE 'Innodb_rows_inserted';

你想想看,如果这三个环节都正常,那延迟大概率是「数据倾斜」造成的。比如某个分区的数据量特别大,导致单个消费者处理不过来。

3.2 连接池耗尽:一个被忽视的「隐形杀手」

连接池耗尽,说白了就是「数据库连接不够用了」。我见过最夸张的一次,一个采集任务开了200个线程,每个线程都从连接池拿连接,结果连接池只有50个连接——直接爆了。

为什么会这样?

我个人总结了几种常见场景:

场景 根因 典型表现
并发采集任务过多 任务数 > 连接池上限 大量「Connection refused」错误
慢SQL占用连接 SQL执行时间过长 连接被长期持有,无法释放
连接泄漏 代码未正确关闭连接 连接数持续增长,直到耗尽
数据库端限制 max_connections设置过小 应用端报错,数据库端也报错

嗯,这里要注意:连接池耗尽不一定是连接池配置的问题。我曾经排查过一个案例,连接池配置了200个连接,按理说够用了。但一查数据库的max_connections,只有100——连接池再大也没用。

警告:连接池耗尽会导致「雪崩效应」。一个任务拿不到连接,会不断重试,重试又加剧连接竞争,最终所有采集任务都挂掉。我建议设置「连接池使用率告警」,超过80%就触发通知。

排查连接池耗尽,我一般这么干:

# 1. 查看当前活跃连接数(MySQL)
SHOW PROCESSLIST;
# 重点关注 Time 列,看是否有长时间未释放的连接

# 2. 查看连接池状态(以HikariCP为例)
# 在应用日志中搜索:
"Pool stats (total=xxx, active=xxx, idle=xxx, waiting=xxx)"

# 3. 检查数据库最大连接数
SHOW VARIABLES LIKE 'max_connections';

解决方案其实不复杂:控制并发数、优化SQL、设置合理的超时时间。我个人习惯把连接池的maximum-pool-size设置为数据库max_connections的60%-70%,留点余量给其他应用。

3.3 全量同步OOM:内存是怎么被吃掉的?

全量同步OOM(Out Of Memory),是数据采集里最「暴力」的问题。我记得有一次,全量同步一张10亿行的表,结果JVM直接报OutOfMemoryError: Java heap space,任务挂了,服务器也差点宕机。

为什么会OOM?说白了就是一次性加载的数据量太大。很多新手写全量同步,喜欢这么干:

// ❌ 错误做法:一次性加载全量数据
List<Record> allRecords = jdbcTemplate.query("SELECT * FROM huge_table");
// 内存直接爆炸

正确的做法是什么?分页查询 + 流式读取。我建议用游标(Cursor)或者分页(Page)的方式,每次只处理一小批数据。

核心原则:全量同步时,内存中同时存在的数据量,不要超过「单批次处理量 × 2」。这样即使有GC压力,也不会OOM。

这里分享一个我常用的分页方案:

// ✅ 正确做法:游标方式(以MyBatis为例)
@Select("SELECT * FROM huge_table")
@Options(fetchSize = 1000, resultSetType = ResultSetType.FORWARD_ONLY)
List<Record> scanAll();

// 或者用分页方式
int pageSize = 5000;
int pageNum = 0;
while (true) {
    List<Record> page = jdbcTemplate.query(
        "SELECT * FROM huge_table LIMIT ? OFFSET ?",
        new Object[]{pageSize, pageNum * pageSize}
    );
    if (page.isEmpty()) break;
    // 处理这一批数据
    processBatch(page);
    pageNum++;
}

你想想看,如果一张表有1亿行,每行1KB,那就是100GB的数据。一次性加载到内存,什么服务器扛得住?

除了分页,还有几个点要注意:

  • 避免在循环中创建大对象——比如在循环里拼接JSON字符串,很容易导致频繁GC
  • 合理设置JVM参数——全量同步任务,我一般把-Xmx设置为服务器内存的50%-60%
  • 监控GC情况——如果Full GC频繁,说明内存压力大,需要减小批次大小

避坑指南:我曾经遇到一个案例,全量同步时用了SELECT *,结果某个字段是TEXT类型,存储了10MB的文本。单条记录就很大,批次一多直接OOM。后来改成只同步需要的字段,问题解决。

3.4 知识体系总览

说了这么多,咱们用一张图来总结一下这三个问题的排查思路:

数据库采集异常排查知识体系 CDC延迟 连接池耗尽 全量同步OOM 排查要点 • 源库日志生成速度 • CDC工具消费能力 • 目标端写入瓶颈 排查要点 • 并发任务数 • 慢SQL占用 • 连接泄漏 排查要点 • 分页/游标使用 • JVM参数设置 • 字段选择优化 核心排查原则 从外到内 → 从宏观到微观 → 先定位再优化

这张图把三个问题的排查要点都串起来了。你排查的时候,按照这个思路走,基本不会漏掉关键环节。

好了,数据库采集异常排查就聊到这儿。这三个问题,说白了都是「资源管理」的问题——要么是时间资源(延迟),要么是连接资源(连接池),要么是内存资源(OOM)。把资源管好了,采集任务就稳了。


公众号:蓝海资料掘金营,微信deep3321