1. 项目概述
在数据库密集型应用中,批处理操作和分页查询是最常见的性能瓶颈点。上周刚处理过一个生产案例:某电商平台的订单同步服务在促销期间出现严重延迟,经排查发现正是由于单条插入和低效分页导致。今天我们就来深度剖析这两个高频问题的优化方案。
2. JDBC批量插入优化
2.1 为什么需要批量处理
当需要插入1000条数据时,用单条INSERT语句循环执行会产生:
- 1000次网络往返
- 1000次SQL解析
- 1000次事务开销 实测对比(MySQL 8.0,10000条数据):
单条插入:12.8秒 批量插入:0.4秒2.2 JDBC Batch核心实现
// 关键配置 connection.setAutoCommit(false); // 关闭自动提交 String sql = "INSERT INTO orders(order_no,user_id) VALUES(?,?)"; PreparedStatement ps = connection.prepareStatement(sql); for (Order order : orderList) { ps.setString(1, order.getNo()); ps.setInt(2, order.getUserId()); ps.addBatch(); // 加入批处理 if(i % 500 == 0){ // 分段提交 ps.executeBatch(); connection.commit(); } } ps.executeBatch(); connection.commit();重要提示:不同数据库对batchSize有不同限制,MySQL建议500-1000,Oracle可到2000
2.3 性能优化参数
在连接字符串添加关键参数:
# MySQL rewriteBatchedStatements=true # 关键! useServerPrepStmts=true cachePrepStmts=true # Oracle defaultExecuteBatch=10003. 分页查询优化方案
3.1 LIMIT偏移量问题分析
SELECT * FROM orders ORDER BY create_time DESC LIMIT 10000, 20;这种写法会导致:
- 先读取10020条记录
- 丢弃前10000条
- 返回剩余20条
3.2 优化方案一:索引覆盖+延迟关联
-- 第一步:通过覆盖索引快速定位 SELECT id FROM orders ORDER BY create_time DESC LIMIT 10000, 20; -- 第二步:回表获取完整数据 SELECT * FROM orders WHERE id IN (上述结果);3.3 优化方案二:游标分页(推荐)
-- 第一页 SELECT * FROM orders ORDER BY create_time DESC, id DESC LIMIT 20; -- 后续页(传入上一页最后记录的create_time和id) SELECT * FROM orders WHERE create_time <= ? AND id < ? ORDER BY create_time DESC, id DESC LIMIT 20;实战经验:必须保证ORDER BY字段唯一性,否则会出现重复或遗漏数据
4. 综合优化案例
某物流系统分页查询优化前后对比:
| 指标 | 原方案(LIMIT) | 优化方案(游标) |
|---|---|---|
| 10000页响应时间 | 2.4秒 | 0.03秒 |
| CPU消耗 | 85% | 12% |
| 内存占用 | 1.2GB | 200MB |
实现代码示例:
public Page<Order> queryByCursor(Long lastId, LocalDateTime lastTime, int size) { String sql = lastId == null ? "SELECT * FROM orders ORDER BY create_time DESC LIMIT ?" : "SELECT * FROM orders WHERE create_time <= ? AND id < ? ORDER BY create_time DESC LIMIT ?"; // ...执行逻辑 }5. 常见问题排查
5.1 批量插入报错处理
现象:BatchUpdateException: Packet too large 解决方案:
-- MySQL调整最大包大小 SET GLOBAL max_allowed_packet=256M;5.2 分页结果不一致
可能原因:
- 排序字段不唯一导致分页漂移
- 数据实时变化影响游标稳定性
应对策略:
- 业务上允许时使用不可变数据(如归档表)
- 必须用可变数据时增加事务隔离
6. 高级技巧
6.1 批量插入的并行优化
// 使用并行流处理 List<Order> batches = Lists.partition(orderList, 500); batches.parallelStream().forEach(batch -> { // 每个batch使用独立连接 try(Connection conn = dataSource.getConnection()) { // 执行批量插入 } });6.2 分页查询的预计算方案
对于超大数据集(如亿级),可采用:
- 定时任务预计算分页结果到缓存
- 使用Elasticsearch等专用查询引擎
- 采用物化视图技术
7. 监控与调优
关键监控指标:
-- MySQL批处理效果检查 SHOW STATUS LIKE 'Com_stmt%'; -- 分页查询性能分析 EXPLAIN ANALYZE SELECT ...;JVM层面建议配置:
# 增大批处理缓冲区 spring.datasource.hikari.data-source-properties.serverPrepStmtCacheSize=500 spring.datasource.hikari.data-source-properties.prepStmtCacheSqlLimit=20488. 不同数据库差异
| 特性 | MySQL | Oracle | PostgreSQL |
|---|---|---|---|
| 批量提交语法 | addBatch() | addBatch() | addBatch() |
| 最优batchSize | 500-1000 | 1000-2000 | 300-500 |
| 分页语法 | LIMIT | ROWNUM | LIMIT/OFFSET |
9. 实战经验总结
- 批处理必须测试不同batchSize,并非越大越好
- 游标分页的排序字段必须创建联合索引
- 高并发场景建议结合连接池配置优化
- 分布式环境需注意ID生成策略对排序的影响
最后分享一个真实案例:某金融系统迁移到游标分页后,API响应时间P99从1200ms降至45ms,同时数据库CPU负载下降60%。这充分说明基础优化的巨大价值。