
1. JDBC连接池高并发场景下的性能救星第一次接触电商后台系统开发时我遇到了一个令人头疼的问题——每天促销活动开始后系统就会变得异常缓慢甚至频繁报错。经过排查发现问题出在数据库连接管理上。每次用户查询商品信息系统都会新建一个数据库连接活动期间瞬时并发量能达到5000MySQL的连接数很快就被耗尽。这时候我才真正理解了连接池的重要性。连接池的核心思想是预先建立并缓存一定数量的数据库连接当应用需要时直接从池中获取用完后归还而不是销毁。这就像在高峰期打车连接池相当于提前预约好的车队而传统方式则是临时在路边拦车效率高低立判。目前主流的开源连接池有HikariCPSpring Boot 2.x默认连接池号称快如闪电Druid阿里开源项目带有完善的监控功能Tomcat JDBC PoolTomcat内置连接池适合Web应用以HikariCP为例基础配置只需要几行代码HikariConfig config new HikariConfig(); config.setJdbcUrl(jdbc:mysql://localhost:3306/ecommerce); config.setUsername(root); config.setPassword(123456); config.setMaximumPoolSize(20); // 最大连接数 config.setMinimumIdle(5); // 最小空闲连接 HikariDataSource ds new HikariDataSource(config);实际项目中我推荐这些关键参数配置maximumPoolSize根据数据库服务器配置设置通常CPU核心数*2 有效磁盘数connectionTimeout获取连接超时时间建议3000-5000msidleTimeout连接空闲超时600000ms(10分钟)是个平衡点maxLifetime连接最大存活时间1800000ms(30分钟)可防止网络抖动问题2. PreparedStatement安全与性能的双重保障去年我们团队遭遇了一次数据泄露事件攻击者通过商品搜索框注入了恶意SQL。这件事让我深刻认识到使用Statement直接拼接SQL就像用纸板做防盗门——形同虚设。而PreparedStatement采用预编译参数绑定机制从根本上杜绝了SQL注入风险。PreparedStatement的优势主要体现在安全性自动处理特殊字符转义性能SQL预编译后可以重复使用可读性参数化查询更清晰来看个实际案例。假设我们要根据商品ID和分类查询// 危险写法容易SQL注入 String sql SELECT * FROM products WHERE id id AND category category ; Statement stmt conn.createStatement(); ResultSet rs stmt.executeQuery(sql); // 安全写法 String sql SELECT * FROM products WHERE id? AND category?; PreparedStatement pstmt conn.prepareStatement(sql); pstmt.setInt(1, id); pstmt.setString(2, category); ResultSet rs pstmt.executeQuery();在电商系统中商品搜索、用户登录这些高频操作必须使用PreparedStatement。我曾做过测试在百万级数据量的商品表中PreparedStatement的查询速度比Statement快15%-20%因为数据库不需要每次都重新解析SQL。3. 批量操作数据导入的速度革命大促前我们需要导入十万级的新商品数据最初采用单条插入的方式整个过程耗时近2小时。后来改用批量操作时间缩短到惊人的3分钟这就是JDBC批量操作的魔力。JDBC提供了两种批量处理方式Statement批量适合不同SQL语句的批量PreparedStatement批量适合相同SQL不同参数的批量以商品批量插入为例String sql INSERT INTO products(name, price, stock) VALUES(?,?,?); PreparedStatement pstmt conn.prepareStatement(sql); // 关闭自动提交提升性能 conn.setAutoCommit(false); for(Product product : productList) { pstmt.setString(1, product.getName()); pstmt.setBigDecimal(2, product.getPrice()); pstmt.setInt(3, product.getStock()); pstmt.addBatch(); // 加入批处理 // 每1000条执行一次 if(i%1000 0) { pstmt.executeBatch(); conn.commit(); } } // 处理剩余记录 pstmt.executeBatch(); conn.commit();几个关键优化点批量大小控制在500-1000最佳关闭自动提交(autocommit)能提升50%以上性能MySQL需要在连接字符串添加rewriteBatchedStatementstrue参数对于特大批量(10万)考虑分多个批次处理4. 事务管理确保数据一致性的关键电商系统中的订单处理是个典型的事务场景扣减库存、生成订单、记录流水必须全部成功或全部失败。我曾遇到过因为事务使用不当导致库存扣减了但订单没生成最终不得不人工补偿的尴尬情况。JDBC事务控制的核心API很简单conn.setAutoCommit(false)开启事务conn.commit()提交事务conn.rollback()回滚事务但实际应用中要注意这些细节隔离级别选择READ_UNCOMMITTED可能读到脏数据READ_COMMITTED解决脏读Oracle默认REPEATABLE_READ解决不可重复读MySQL默认SERIALIZABLE最高隔离级别性能最差设置方法conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);保存点(Savepoint)的使用 对于复杂事务可以在中间设置保存点实现部分回滚Savepoint sp1 conn.setSavepoint(SP1); try { // 某些操作 } catch(Exception e) { conn.rollback(sp1); // 只回滚到SP1 conn.commit(); // 提交其他操作 }超时设置 长时间运行的事务会锁定资源应该设置超时stmt.setQueryTimeout(30); // 30秒超时在分布式系统中单机事务已经不能满足需求这时候需要考虑Spring的Transactional注解或Seata这类分布式事务框架。但无论如何理解JDBC原生事务是基础中的基础。5. 结果集处理内存与效率的平衡艺术处理百万级查询结果时一不小心就会导致JVM内存溢出。有一次我直接使用ResultSet处理商品导出系统直接OOM崩溃。后来学会了正确的处理方式分享几个实用技巧流式查询 对于大数据集使用TYPE_FORWARD_ONLY和CONCUR_READ_ONLY模式并设置fetchSizeStatement stmt conn.createStatement( ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); stmt.setFetchSize(1000); ResultSet rs stmt.executeQuery(SELECT * FROM big_table);分批处理 不要一次性获取所有数据而是分页处理int pageSize 1000; int offset 0; while(true) { String sql String.format( SELECT * FROM products LIMIT %d OFFSET %d, pageSize, offset); ResultSet rs stmt.executeQuery(sql); if(!rs.next()) break; // 处理本页数据 offset pageSize; }行映射优化 避免在循环中反复通过列名获取数据应该// 低效写法 while(rs.next()) { String name rs.getString(name); BigDecimal price rs.getBigDecimal(price); // ... } // 高效写法 int nameIdx rs.findColumn(name); int priceIdx rs.findColumn(price); while(rs.next()) { String name rs.getString(nameIdx); BigDecimal price rs.getBigDecimal(priceIdx); // ... }对于现代Java项目推荐使用JdbcTemplate或MyBatis等ORM框架它们已经内置了很多优化。但在性能敏感场景下理解底层JDBC优化原理仍然非常重要。6. 连接池监控与故障排查线上环境最怕连接池出问题要么连接泄漏导致池子耗尽要么配置不性能低下。分享几个实战中总结的监控和排查方法Druid监控配置// 在Druid配置中启用监控 druidDataSource.setFilters(stat,wall); // 访问监控页面 // http://localhost:8080/druid/index.html常见问题排查连接泄漏检查是否所有Connection都正确关闭连接数不足观察wait_thread_count指标性能下降检查validationQuery是否配置合理健康检查配置// HikariCP健康检查 config.setConnectionTestQuery(SELECT 1); config.setHealthCheckRegistry(new HealthCheckRegistry());日志监控 建议记录这些关键指标活跃连接数空闲连接数等待获取连接的线程数连接获取平均时间在Spring Boot中可以通过Actuator端点监控HikariCPmanagement.endpoints.web.exposure.includehealth,info,metrics访问/actuator/metrics/hikaricp.connections可以看到详细连接池指标。我曾经通过这个发现了一个连接泄漏的Bug某个定时任务没有正确关闭连接导致每天凌晨连接数都会缓慢增长直到耗尽。