1. 数据库批量插入性能优化实战
作为一名长期奋战在一线的Java开发者,我经常需要处理海量数据导入的场景。今天要分享的是我在实际项目中积累的一套完整的数据库优化方案,涵盖了从批量插入优化、唯一编号生成到慢SQL监控的全套解决方案。
1.1 告别低效的foreach批量插入
很多开发者习惯在MyBatis中使用foreach标签进行批量插入,像这样:
<insert id="insertList" parameterType="list"> insert into t_sample (id, sampleNo, sampleName) values <foreach collection="list" item="item" separator=","> (#{item.id}, #{item.sampleNo}, #{item.sampleName}) </foreach> </insert>这种方式的致命缺陷在于:当数据量达到数千条时,会生成一条极其庞大的SQL语句。我曾经遇到过一次性插入5000条记录,生成的SQL超过1MB,不仅导致内存压力大,而且MySQL服务器解析这样的SQL也会消耗大量CPU资源。
优化方案:启用rewriteBatchedStatements参数 + MyBatis-Plus的saveBatch方法
- 在application.properties中添加参数:
spring.datasource.url=jdbc:mysql://localhost:3306/db?rewriteBatchedStatements=true- 替换Service层代码:
// 旧方式(性能差) // mapper.insertList(dataList); // 新方式(高性能) this.saveBatch(dataList, 1000); // 每1000条批量提交一次技术原理:rewriteBatchedStatements=true会启用JDBC的批量操作优化,将多个INSERT语句转换为高效的二进制协议格式传输。实测在插入1万条记录时,性能提升可达5-8倍。
1.2 批量插入的黄金参数配置
除了rewriteBatchedStatements,还有几个关键参数会影响批量插入性能:
# 连接池配置(使用Druid为例) spring.datasource.druid.max-active=50 # 根据服务器配置调整 spring.datasource.druid.initial-size=10 # 初始化连接数 spring.datasource.druid.min-idle=10 # 最小空闲连接 # MySQL特有优化 spring.datasource.druid.connection-properties=useServerPrepStmts=true;cachePrepStmts=true;prepStmtCacheSize=250;prepStmtCacheSqlLimit=2048避坑指南:
- 批量大小建议设置在500-2000之间,太小达不到批量效果,太大会增加内存压力
- 事务范围要合理控制,避免一个事务包含太多插入操作
- 在高并发场景下,需要适当增加连接池大小
2. 高并发下的唯一编号生成方案
在批量导入场景中,经常需要为每条记录生成唯一编号。常见的方案各有优劣,需要根据业务场景选择。
2.1 传统方案的问题
很多系统会采用时间戳+随机数的方式:
String uniqueNo = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) + (int)(Math.random()*90+10);这种方案在大批量导入时容易出现重复,特别是在分布式环境下几乎无法保证唯一性。
2.2 Redis原子计数器方案
@Autowired private StringRedisTemplate stringRedisTemplate; public String generateUniqueNo() { String dateStr = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")); String redisKey = "order:seq:" + dateStr; Long seq = stringRedisTemplate.opsForValue().increment(redisKey); if(seq == 1) { // 设置24小时过期 stringRedisTemplate.expire(redisKey, 24, TimeUnit.HOURS); } return dateStr + String.format("%04d", seq); }优势:
- 绝对唯一且有序
- 性能极高(Redis每秒可处理10万+次自增操作)
- 天然支持分布式环境
注意事项:
- Redis需要做高可用部署,避免单点故障
- 序列号长度要预留足够(如%04d表示最多9999个序号)
- 业务日期变更时需要自动切换Key
2.3 雪花算法(Snowflake)方案
MyBatis-Plus内置了雪花算法实现:
String id = IdWorker.getIdStr(); // 生成19位数字ID算法结构:
0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000 | | | | | | 符号位(1) 时间戳(41) 数据中心ID(5) 机器ID(5) 序列号(12)时钟回拨问题解决方案:
- 使用MyBatis-Plus的
IdentifierGenerator接口自定义实现 - 在检测到时钟回拨时,可以:
- 短暂等待时钟同步
- 使用备用ID生成方案(如Redis)
- 抛出明确异常告警
public class CustomIdGenerator implements IdentifierGenerator { @Override public Long nextId(Object entity) { // 自定义时钟回拨处理逻辑 try { return Snowflake.getInstance().nextId(); } catch (ClockBackwardsException e) { // 降级方案 return redisTemplate.opsForValue().increment("backup:id"); } } }3. 数据库安全防护措施
3.1 防止全表更新/删除
MyBatis-Plus提供了BlockAttackInnerInterceptor拦截器:
@Configuration public class MybatisConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor()); return interceptor; } }这个拦截器会检查所有UPDATE和DELETE语句:
- 如果没有WHERE条件,直接抛出异常
- 如果WHERE条件中只有永真条件(如1=1),也会被拦截
3.2 SQL注入防护
除了使用MyBatis的预编译机制外,Druid还提供了额外的防护:
spring: datasource: druid: filter: wall: enabled: true config: delete-allow: false # 禁止delete语句 drop-table-allow: false # 禁止删表 none-base-statement-allow: false # 禁止非基础SQL4. 慢SQL监控方案对比
4.1 MySQL原生慢查询日志
-- 开启慢查询日志 SET GLOBAL slow_query_log = 'ON'; -- 设置慢查询阈值(秒) SET GLOBAL long_query_time = 1; -- 记录未使用索引的查询 SET GLOBAL log_queries_not_using_indexes = 'ON';分析工具:
mysqldumpslow -s t /var/lib/mysql/mysql-slow.log优缺点:
- 优点:对性能影响小,记录全面
- 缺点:需要访问服务器,实时性差
4.2 p6spy方案
配置步骤:
- 添加依赖:
<dependency> <groupId>p6spy</groupId> <artifactId>p6spy</artifactId> <version>3.9.1</version> </dependency>- 修改数据源配置:
spring.datasource.driver-class-name=com.p6spy.engine.spy.P6SpyDriver spring.datasource.url=jdbc:p6spy:mysql://localhost:3306/db- 配置spy.properties:
appender=com.p6spy.engine.spy.appender.Slf4JLogger executionThreshold=1000 # 慢查询阈值(ms)适用场景:
- 开发环境调试
- 测试环境性能分析
4.3 Druid监控方案
完整配置示例:
spring: datasource: type: com.alibaba.druid.pool.DruidDataSource druid: filters: stat,wall filter: stat: enabled: true slow-sql-millis: 1000 log-slow-sql: true stat-view-servlet: enabled: true login-username: admin login-password: admin监控大屏功能:
- SQL监控:统计SQL执行次数、耗时等
- SQL防火墙:拦截恶意SQL
- Web应用监控:统计URI请求量
- 连接池监控:活跃连接数等
生产环境建议:
- 设置合理的慢SQL阈值(通常1-2秒)
- 定期分析慢SQL日志
- 对高频慢SQL建立索引优化机制
5. 实战经验总结
在最近的一个供应链管理系统中,我应用了这套优化方案:
批量插入优化:将5万条物料数据的导入时间从原来的3分钟缩短到15秒
编号生成方案:采用Redis+雪花算法的混合模式:
- 日常小批量操作使用雪花算法
- 批量导入时切换为Redis序列
- 实现了零重复和百万级/天的编号生成能力
慢SQL监控:通过Druid发现了3个关键索引缺失问题,优化后系统响应时间提升40%
特别提醒:在实施这些优化时,一定要根据实际业务场景进行调整。比如:
- 对实时性要求高的系统,可以适当降低慢SQL阈值
- 在资源受限的环境中,需要权衡监控粒度和性能开销
- 分布式ID生成要考虑业务连续性需求
这套方案经过多个生产环境验证,能够显著提升数据库操作性能和可靠性。特别是在处理大批量数据时,合理组合这些技术可以避免很多潜在问题。