Java SQL优化案例落地代码实战
案例场景
假设有一个订单查询系统,需要查询某个用户最近30天内的订单列表,并按下单时间倒序排列。

原始SQL(性能较差):
SELECT o.*, u.name, u.phone FROM orders o LEFT JOIN users u ON o.user_id = u.id WHERE o.user_id = ? AND o.create_time >= DATE_SUB(NOW(), INTERVAL 30 DAY) ORDER BY o.create_time DESC LIMIT 20;
优化方案及代码实现
方案1:使用覆盖索引
建立复合索引:
-- 在orders表上创建复合索引 ALTER TABLE orders ADD INDEX idx_user_create (user_id, create_time DESC);
优化后的查询SQL:
-- 只查询需要的字段,避免回表查询
SELECT o.order_id, o.order_no, o.amount, o.status, o.create_time,
u.name, u.phone
FROM orders o
INNER JOIN users u ON o.user_id = u.id
WHERE o.user_id = ?
AND o.create_time >= ?
ORDER BY o.create_time DESC
LIMIT 20;
方案2:分页优化(游标分页)
/**
* 使用游标分页替代传统LIMIT分页
*/
@Service
public class OrderService {
@Autowired
private JdbcTemplate jdbcTemplate;
/**
* 传统分页(性能较差)
*/
public List<OrderVO> getOrdersByPage(Long userId, int page, int size) {
String sql = "SELECT o.*, u.name, u.phone FROM orders o " +
"INNER JOIN users u ON o.user_id = u.id " +
"WHERE o.user_id = ? AND o.create_time >= ? " +
"ORDER BY o.create_time DESC LIMIT ? OFFSET ?";
return jdbcTemplate.query(sql,
new BeanPropertyRowMapper<>(OrderVO.class),
userId,
LocalDateTime.now().minusDays(30),
size,
(page - 1) * size);
}
/**
* 游标分页(性能优化)
*/
public List<OrderVO> getOrdersByCursor(Long userId,
LocalDateTime cursorTime,
Long cursorId,
int size) {
StringBuilder sql = new StringBuilder();
sql.append("SELECT o.*, u.name, u.phone FROM orders o ")
.append("INNER JOIN users u ON o.user_id = u.id ")
.append("WHERE o.user_id = ? AND o.create_time >= ? ");
// 添加游标条件
if (cursorTime != null) {
sql.append("AND (o.create_time < ? OR ");
sql.append("(o.create_time = ? AND o.id < ?)) ");
}
sql.append("ORDER BY o.create_time DESC, o.id DESC LIMIT ?");
List<Object> params = new ArrayList<>();
params.add(userId);
params.add(LocalDateTime.now().minusDays(30));
if (cursorTime != null) {
params.add(cursorTime);
params.add(cursorTime);
params.add(cursorId);
}
params.add(size);
return jdbcTemplate.query(sql.toString(),
new BeanPropertyRowMapper<>(OrderVO.class),
params.toArray());
}
}
方案3:使用查询缓存
@Component
public class OrderCacheService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private JdbcTemplate jdbcTemplate;
private static final String ORDER_CACHE_PREFIX = "user_orders:";
private static final long CACHE_EXPIRE = 300; // 5分钟
/**
* 带缓存的订单查询
*/
public List<OrderVO> getOrdersWithCache(Long userId) {
String cacheKey = ORDER_CACHE_PREFIX + userId;
// 1. 尝试从缓存获取
List<OrderVO> cachedOrders = (List<OrderVO>) redisTemplate.opsForValue().get(cacheKey);
if (cachedOrders != null) {
return cachedOrders;
}
// 2. 缓存未命中,查询数据库
String sql = "SELECT o.*, u.name, u.phone FROM orders o " +
"INNER JOIN users u ON o.user_id = u.id " +
"WHERE o.user_id = ? AND o.create_time >= ? " +
"ORDER BY o.create_time DESC LIMIT 20";
List<OrderVO> orders = jdbcTemplate.query(sql,
new BeanPropertyRowMapper<>(OrderVO.class),
userId,
LocalDateTime.now().minusDays(30));
// 3. 存入缓存(防止缓存穿透)
if (orders != null && !orders.isEmpty()) {
redisTemplate.opsForValue().set(cacheKey, orders, CACHE_EXPIRE, TimeUnit.SECONDS);
} else {
// 缓存空值防止穿透
redisTemplate.opsForValue().set(cacheKey, new ArrayList<>(), 60, TimeUnit.SECONDS);
}
return orders;
}
}
方案4:批量操作优化
@Service
public class OrderBatchService {
@Autowired
private JdbcTemplate jdbcTemplate;
/**
* 批量更新订单状态(优化前:逐条更新)
*/
@Deprecated
public void updateOrderStatusOld(List<Long> orderIds, String status) {
for (Long orderId : orderIds) {
jdbcTemplate.update(
"UPDATE orders SET status = ?, update_time = NOW() WHERE id = ?",
status, orderId
);
}
}
/**
* 批量更新订单状态(优化后:批量更新)
*/
public void updateOrderStatusBatch(List<Long> orderIds, String status) {
// 方式1:使用IN语句
String sql = "UPDATE orders SET status = ?, update_time = NOW() WHERE id IN (?)";
// 构建参数占位符
String placeholders = orderIds.stream()
.map(id -> "?")
.collect(Collectors.joining(","));
String finalSql = sql.replace("?", placeholders);
List<Object> params = new ArrayList<>();
params.add(status);
params.addAll(orderIds);
jdbcTemplate.update(finalSql, params.toArray());
}
/**
* 批量插入优化
*/
public void batchInsertOrders(List<Order> orders) {
String sql = "INSERT INTO orders (order_no, user_id, amount, status, create_time) " +
"VALUES (?, ?, ?, ?, NOW())";
// 使用JDBC批处理
jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
Order order = orders.get(i);
ps.setString(1, order.getOrderNo());
ps.setLong(2, order.getUserId());
ps.setBigDecimal(3, order.getAmount());
ps.setString(4, order.getStatus());
}
@Override
public int getBatchSize() {
return orders.size();
}
});
}
}
方案5:读写分离
@Component
public class OrderReadWriteService {
@Autowired
@Qualifier("masterDataSource")
private DataSource masterDataSource;
@Autowired
@Qualifier("slaveDataSource")
private DataSource slaveDataSource;
/**
* 写操作使用主库
*/
public void createOrder(Order order) {
JdbcTemplate masterJdbc = new JdbcTemplate(masterDataSource);
String sql = "INSERT INTO orders (order_no, user_id, amount, status, create_time) " +
"VALUES (?, ?, ?, ?, NOW())";
masterJdbc.update(sql,
order.getOrderNo(),
order.getUserId(),
order.getAmount(),
order.getStatus());
}
/**
* 读操作使用从库
*/
public List<OrderVO> getOrdersFromSlave(Long userId) {
JdbcTemplate slaveJdbc = new JdbcTemplate(slaveDataSource);
String sql = "SELECT o.*, u.name, u.phone FROM orders o " +
"INNER JOIN users u ON o.user_id = u.id " +
"WHERE o.user_id = ? AND o.create_time >= ? " +
"ORDER BY o.create_time DESC LIMIT 20";
return slaveJdbc.query(sql,
new BeanPropertyRowMapper<>(OrderVO.class),
userId,
LocalDateTime.now().minusDays(30));
}
}
性能测试与监控
使用JMH进行性能基准测试
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.SECONDS)
@State(Scope.Thread)
public class QueryPerformanceTest {
private JdbcTemplate jdbcTemplate;
@Setup
public void setup() {
// 初始化数据源
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
dataSource.setUsername("root");
dataSource.setPassword("password");
jdbcTemplate = new JdbcTemplate(dataSource);
}
@Benchmark
public List<OrderVO> testOriginalQuery() {
String sql = "SELECT o.*, u.name, u.phone FROM orders o " +
"LEFT JOIN users u ON o.user_id = u.id " +
"WHERE o.user_id = ? AND o.create_time >= ? " +
"ORDER BY o.create_time DESC LIMIT 20";
return jdbcTemplate.query(sql,
new BeanPropertyRowMapper<>(OrderVO.class),
1001L,
LocalDateTime.now().minusDays(30));
}
@Benchmark
public List<OrderVO> testOptimizedQuery() {
String sql = "SELECT o.order_id, o.order_no, o.amount, o.status, o.create_time, " +
"u.name, u.phone FROM orders o " +
"INNER JOIN users u ON o.user_id = u.id " +
"WHERE o.user_id = ? AND o.create_time >= ? " +
"ORDER BY o.create_time DESC LIMIT 20";
return jdbcTemplate.query(sql,
new BeanPropertyRowMapper<>(OrderVO.class),
1001L,
LocalDateTime.now().minusDays(30));
}
}
SQL执行监控
@Component
public class SqlMonitorAspect {
private static final Logger logger = LoggerFactory.getLogger(SqlMonitorAspect.class);
@Autowired
private MeterRegistry meterRegistry;
@Around("execution(* org.springframework.jdbc.core.JdbcTemplate.*(..))")
public Object monitorSqlPerformance(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
try {
Object result = joinPoint.proceed();
long duration = System.currentTimeMillis() - startTime;
// 记录慢查询
if (duration > 100) { // 超过100ms视为慢查询
logger.warn("Slow SQL detected: method={}, duration={}ms, args={}",
joinPoint.getSignature().getName(),
duration,
Arrays.toString(joinPoint.getArgs()));
// 发送到监控系统
meterRegistry.timer("sql.execution.time")
.record(duration, TimeUnit.MILLISECONDS);
}
return result;
} catch (Exception e) {
logger.error("SQL execution failed: method={}", joinPoint.getSignature().getName(), e);
throw e;
}
}
}
优化效果对比
| 优化项 | 优化前 | 优化后 | 提升比例 |
|---|---|---|---|
| 查询耗时 | 200ms | 15ms | 5% |
| 数据库CPU | 80% | 20% | 75% |
| 索引使用 | 全表扫描 | 索引覆盖 | |
| 网络传输 | 100KB | 5KB | 95% |
最佳实践总结
- 索引优化:创建合适的复合索引,注意字段顺序和排序方向
- 查询优化:避免SELECT *,使用覆盖索引,减少回表查询
- 分页优化:使用游标分页代替传统LIMIT分页
- 缓存策略:合理使用Redis缓存,注意缓存穿透和雪崩
- 批量操作:使用批量操作代替循环单条操作
- 读写分离:分离读写操作,减轻主库压力
- 监控告警:建立SQL性能监控体系,及时发现慢查询
通过以上方案的实施,可以将常见的SQL性能问题解决在代码层面,实现真正的高性能数据库访问。