关键点:为什么塔内要逐行读取?
2026/7/3 21:17:49 网站建设 项目流程

SQL文件可能很大

单个SQL文件可能达到几百MB(如50万行数据),如果一次性读取:

  • 内存占用过高:100MB文件加载需要几百MB+内存,而且多线程处理更容易造成OOM
  • GC压力大:大对象频繁创建和回收

原因二:无法按普通分号切割

如果用;切割会出错:

// ❌ 错误做法 String[] sqls = allContent.split(";"); // 会误切数据里的分号!

正确做法:逐行拼接,遇到;#END#才算完整

// ✅ 正确做法 StringBuilder currentSql = new StringBuilder(); while ((line = reader.readLine()) != null) { currentSql.append(line); if (currentSql.toString().endsWith(";#END#")) { String sql = currentSql.toString().replace(";#END#", ";"); executeBatch(sql); currentSql.setLength(0); // 清空,准备下一条 } }

SQL文件格式示例

DELETE FROM `table` WHERE id = 1;#END# INSERT INTO `table` VALUES (1, 'data;with;semicolons');#END# INSERT INTO `table` VALUES (2, 'line1\nline2');#END#

第三难:同步策略多样化,怎么灵活配置?

背景:四种同步策略

同步策略适用场景SQL操作数据范围
全表同步基础配置表(数据量小,千行级)TRUNCATE+INSERT整张表的所有数据
公司级条件同步按公司维度管理的表DELETE WHERE company_id=?+INSERT单个公司的所有数据
店铺级增量同步有软删除标记和更新时间的表DELETE WHERE shop_id=? AND ...+INSERT单店铺增量数据
店铺级全量同步物理删除的表DELETE WHERE shop_id=?+INSERT单店铺全部数据

问题:100+张表里,四种策略混杂,查询条件各不相同。需要灵活配置每张表的同步策略和WHERE条件。

解决方案:配置驱动 + 占位符

核心思想:把同步策略、查询条件放到配置表里,每张表单独配置

配置表设计
CREATE TABLE `sync_config` ( `id` int PRIMARY KEY, `table_name` varchar(100), `table_level` varchar(20), -- company/shop `sync_type` int, -- 0:全表, 1:条件同步 `where_condition` text, -- WHERE条件模板(支持占位符) `delete_strategy` varchar(20) -- TRUNCATE/DELETE );
配置示例
-- 全表同步 INSERT INTO sync_config VALUES (1, 'sys_config', 'company', 0, NULL, 'TRUNCATE'); -- 公司级条件同步 INSERT INTO sync_config VALUES (2, 'company_settings', 'company', 1, 'company_id = {companyId} AND status = 1', 'DELETE'); -- 店铺级增量同步 INSERT INTO sync_config VALUES (3, 'user_table', 'shop', 1, 'shop_id = {shopId} AND update_time > {lastTime}', 'DELETE'); -- 店铺级全量同步 INSERT INTO sync_config VALUES (4, 'order_table', 'shop', 1, 'shop_id = {shopId}', 'DELETE');
占位符替换逻辑
private String buildWhereCondition(String template, SyncContext ctx) { if (template == null) return ""; // 全表同步,无WHERE条件 return template .replace("{shopId}", String.valueOf(ctx.getShopId())) .replace("{companyId}", String.valueOf(ctx.getCompanyId())) .replace("{lastTime}", ctx.getLastSyncTime()); }

SQL生成过程(以店铺级增量同步为例)

步骤1:构造查询SQL
// 占位符替换后得到WHERE条件 String whereCondition = "shop_id = 123 AND update_time > '2025-01-15 00:00:00'"; // 构造SELECT语句 String selectSql = "SELECT * FROM user_table WHERE " + whereCondition;
步骤2:流式读取并生成SQL文件

关键点:从ResultSet元数据动态获取字段,而非写死字段名

try (ResultSet rs = stmt.executeQuery(selectSql)) { ResultSetMetaData metadata = rs.getMetaData(); int columnCount = metadata.getColumnCount(); // 从元数据获取列名列表 List<String> columnNames = new ArrayList<>(); for (int i = 1; i <= columnCount; i++) { columnNames.add(metadata.getColumnName(i)); } // 1. 先写DELETE语句 writer.write("DELETE FROM user_table WHERE " + whereCondition + ";#END#"); writer.write(System.lineSeparator()); // 2. 构造INSERT语句头部(字段名从元数据获取) String insertHeader = "INSERT INTO `user_table` (" + String.join(", ", columnNames) + ") VALUES\n"; StringBuilder values = new StringBuilder(); int batchCount = 0; // 3. 流式读取数据并拼接VALUES while (rs.next()) { values.append("("); for (int i = 1; i <= columnCount; i++) { if (i > 1) values.append(", "); // 根据字段类型格式化值(动态处理) values.append(formatValue(rs, i, metadata.getColumnType(i))); } values.append(")"); batchCount++; // 每10行生成一条INSERT if (batchCount >= 10) { writer.write(insertHeader + values.toString() + ";#END#"); writer.write(System.lineSeparator()); values.setLength(0); batchCount = 0; } else { values.append(", "); } } // 4. 处理剩余数据 if (batchCount > 0) { writer.write(insertHeader + values.toString() + ";#END#"); } }
最终生成的SQL文件
DELETE FROM user_table WHERE shop_id = 123 AND update_time > '2025-01-15 00:00:00';#END# INSERT INTO `user_table` (id, shop_id, username, update_time) VALUES (1, 123, 'Alice', '2025-01-16 10:00:00'), (2, 123, 'Bob', '2025-01-16 11:00:00');#END#

优势总结

灵活性:四种策略自由配置,满足不同表的需求
可扩展:新增表只需加配置,代码零改动
占位符:支持{shopId}{companyId}{lastTime}等动态参数
零硬编码:字段名从元数据动态获取,适配任意表结构


第四难:单表50W+数据,如何防止OOM?

问题:传统方式的内存杀手

// 反面教材:一次性加载全部数据 String sql = "SELECT * FROM huge_table WHERE shop_id = 123"; List<Map<String, Object>> allRows = jdbcTemplate.queryForList(sql); // 直接OOM

单店铺单表可能50W+行,全部加载到内存会导致OutOfMemoryError。

解决方案:流式读取 + 临时文件

MySQL流式读取
private void generateSQL(DataSource ds, String sql) throws SQLException { try (Connection conn = ds.getConnection(); Statement stmt = conn.createStatement( ResultSet.TYPE_FORWARD_ONLY, // 只向前遍历 ResultSet.CONCUR_READ_ONLY)) { // 只读模式 // 核心:启用MySQL流式读取 stmt.setFetchSize(Integer.MIN_VALUE); // MySQL JDBC特殊约定! try (ResultSet rs = stmt.executeQuery(sql)) { int batchCount = 0; StringBuilder sqlValues = new StringBuilder(); while (rs.next()) { // 逐行处理 sqlValues.append("("); for (int i = 1; i <= columnCount; i++) { sqlValues.append(formatValue(rs, i)); } sqlValues.append(")"); batchCount++; // 每10行生成一条INSERT if (batchCount >= 10) { writeInsert(sqlValues.toString()); sqlValues.setLength(0); // 清空缓冲 batchCount = 0; } } } } }

核心技巧

  • stmt.setFetchSize(Integer.MIN_VALUE):MySQL JDBC的特殊约定,启用服务器端游标
  • 每次只拉取1行数据到客户端,内存占用恒定
  • 批量拼接VALUES:多行生成一条INSERT,减少SQL数量
MongoDB流式读取
CloseableIterator<Document> iterator = mongoTemplate.stream(query, Document.class, collectionName); try { while (iterator.hasNext()) { Document doc = iterator.next(); // 逐文档处理 processDocument(doc); } } finally { iterator.close(); // ⚠️ 必须手动关闭,否则连接泄漏! }

塔内执行:流式读取

try (BufferedReader reader = new BufferedReader( new InputStreamReader(ossStream))) { List<String> sqlBatch = new ArrayList<>(); StringBuilder currentSql = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { // 拼接当前行 currentSql.append(line); // 检查是否是完整的SQL(以;#END#结尾) if (currentSql.toString().endsWith(";#END#")) { // 还原:特殊符号 → 正常分号 String realSql = currentSql.toString().replace(";#END#", ";"); // 添加到批次 sqlBatch.add(realSql); currentSql.setLength(0); // 清空,准备下一条SQL // 批量执行(每100条一批,塔外10条数据构造成1个insert语句) if (sqlBatch.size() >= 100) { executeBatch(stmt, sqlBatch); sqlBatch.clear(); } } } // 执行剩余SQL if (!sqlBatch.isEmpty()) { executeBatch(stmt, sqlBatch); } // 关键:自动提交,避免事务过大 conn.setAutoCommit(true); }

为什么setAutoCommit(true)

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询