本文目录导读:

下面通过一个完整的实际案例,详细展示Java大表查询优化的全过程。
案例背景
业务场景:电商订单系统,订单表包含5000万+数据 表结构:
CREATE TABLE `t_order` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `order_no` varchar(64) NOT NULL COMMENT '订单号', `user_id` bigint(20) NOT NULL COMMENT '用户ID', `product_id` bigint(20) NOT NULL COMMENT '商品ID', `order_status` int(11) NOT NULL COMMENT '订单状态', `order_amount` decimal(10,2) NOT NULL COMMENT '订单金额', `create_time` datetime NOT NULL COMMENT '创建时间', `update_time` datetime NOT NULL COMMENT '更新时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
优化前:问题代码
@Service
public class OrderService {
/**
* 问题1:全表扫描查询
*/
public List<Order> queryOrders(String keyword, Date startTime, Date endTime) {
String sql = "SELECT * FROM t_order WHERE order_no LIKE '%' ? '%'" +
" AND create_time BETWEEN ? AND ?";
// 直接查询,无分页,无索引优化
return jdbcTemplate.query(sql, new Object[]{keyword, startTime, endTime},
new BeanPropertyRowMapper<>(Order.class));
}
/**
* 问题2:N+1查询问题
*/
public List<OrderDetail> getOrderDetails(List<Long> orderIds) {
List<OrderDetail> details = new ArrayList<>();
for (Long orderId : orderIds) {
// 循环查询,每行数据都发一次SQL
OrderDetail detail = orderDetailMapper.selectByOrderId(orderId);
details.add(detail);
}
return details;
}
/**
* 问题3:统计查询无优化
*/
public BigDecimal getTotalAmount(Long userId) {
String sql = "SELECT SUM(order_amount) FROM t_order WHERE user_id = ?";
return jdbcTemplate.queryForObject(sql, new Object[]{userId}, BigDecimal.class);
}
}
优化方案一:索引优化
-- 1. 创建复合索引(遵循最左前缀原则)
ALTER TABLE t_order ADD INDEX idx_user_create (user_id, create_time);
ALTER TABLE t_order ADD INDEX idx_order_no (order_no);
ALTER TABLE t_order ADD INDEX idx_create_time (create_time);
-- 2. 创建覆盖索引(避免回表)
ALTER TABLE t_order ADD INDEX idx_user_amount (user_id, order_amount);
-- 3. 冗余字段优化,避免LIKE查询
ALTER TABLE t_order ADD COLUMN order_prefix varchar(8)
GENERATED ALWAYS AS (LEFT(order_no, 8)) STORED;
ALTER TABLE t_order ADD INDEX idx_order_prefix (order_prefix);
优化方案二:分页查询优化
@Service
@Slf4j
public class OptimizedOrderService {
@Autowired
private JdbcTemplate jdbcTemplate;
/**
* 优化1:使用游标分页(基于索引的分页)
*/
public PageResult<Order> queryOrdersByCursor(Long lastOrderId,
Integer pageSize,
Date startTime,
Date endTime) {
StringBuilder sql = new StringBuilder();
List<Object> params = new ArrayList<>();
sql.append("SELECT id, order_no, user_id, product_id, ")
.append("order_status, order_amount, create_time ")
.append("FROM t_order WHERE 1=1 ");
// 利用id主键索引作为游标
if (lastOrderId != null && lastOrderId > 0) {
sql.append("AND id > ? ");
params.add(lastOrderId);
}
if (startTime != null) {
sql.append("AND create_time >= ? ");
params.add(startTime);
}
if (endTime != null) {
sql.append("AND create_time < ? ");
params.add(endTime);
}
sql.append("ORDER BY id ASC LIMIT ?");
params.add(pageSize + 1); // 多查一条判断是否有下一页
List<Order> orders = jdbcTemplate.query(sql.toString(), params.toArray(),
new BeanPropertyRowMapper<>(Order.class));
// 判断是否有下一页
boolean hasNext = orders.size() > pageSize;
if (hasNext) {
orders = orders.subList(0, pageSize);
}
return new PageResult<>(orders, hasNext,
orders.isEmpty() ? 0 : orders.get(orders.size()-1).getId());
}
/**
* 优化2:延迟关联分页(先查ID再查数据)
*/
public List<Order> queryOrdersWithDeferredJoin(Long offset, Integer pageSize,
Date startTime, Date endTime) {
// 第一步:只查询主键ID(利用覆盖索引)
String countSql = "SELECT id FROM t_order " +
"WHERE create_time BETWEEN ? AND ? " +
"ORDER BY create_time DESC LIMIT ?, ?";
List<Long> ids = jdbcTemplate.queryForList(countSql, Long.class,
startTime, endTime, offset, pageSize);
if (ids.isEmpty()) {
return Collections.emptyList();
}
// 第二步:根据ID批量查询完整数据
String placeholders = String.join(",", Collections.nCopies(ids.size(), "?"));
String detailSql = "SELECT * FROM t_order WHERE id IN (" + placeholders + ") " +
"ORDER BY create_time DESC";
return jdbcTemplate.query(detailSql, ids.toArray(),
new BeanPropertyRowMapper<>(Order.class));
}
}
优化方案三:批量操作优化
@Service
@Slf4j
public class BatchOrderService {
/**
* 优化3:分批查询,解决IN查询限制
*/
public Map<Long, OrderDetail> batchQueryOrderDetails(List<Long> orderIds) {
Map<Long, OrderDetail> resultMap = new HashMap<>();
// 每次查询500条
int batchSize = 500;
for (int i = 0; i < orderIds.size(); i += batchSize) {
List<Long> batchIds = orderIds.subList(i,
Math.min(i + batchSize, orderIds.size()));
String placeholders = String.join(",", Collections.nCopies(batchIds.size(), "?"));
String sql = "SELECT * FROM t_order_detail WHERE order_id IN (" +
placeholders + ")";
List<OrderDetail> details = jdbcTemplate.query(sql, batchIds.toArray(),
new BeanPropertyRowMapper<>(OrderDetail.class));
for (OrderDetail detail : details) {
resultMap.put(detail.getOrderId(), detail);
}
}
return resultMap;
}
/**
* 优化4:批量更新
*/
@Transactional
public void batchUpdateOrderStatus(List<Long> orderIds, Integer status) {
int batchSize = 500;
for (int i = 0; i < orderIds.size(); i += batchSize) {
List<Long> batch = orderIds.subList(i, Math.min(i + batchSize, orderIds.size()));
String sql = "UPDATE t_order SET order_status = ?, update_time = NOW() " +
"WHERE id IN (" + String.join(",", Collections.nCopies(batch.size(), "?")) + ")";
List<Object> params = new ArrayList<>();
params.add(status);
params.addAll(batch);
jdbcTemplate.update(sql, params.toArray());
}
}
}
优化方案四:汇总统计优化
@Service
@Slf4j
public class StatisticsService {
/**
* 优化5:使用缓存汇总统计
*/
@Cacheable(value = "userOrderStats", key = "#userId",
unless = "#result == null")
public UserOrderStats getUserOrderStats(Long userId) {
// 查询汇总统计(利用覆盖索引)
String sql = "SELECT COUNT(*) as order_count, " +
"COALESCE(SUM(order_amount), 0) as total_amount, " +
"AVG(order_amount) as avg_amount " +
"FROM t_order " +
"WHERE user_id = ? " + // 使用idx_user_amount覆盖索引
"AND create_time >= DATE_SUB(NOW(), INTERVAL 1 YEAR)";
return jdbcTemplate.queryForObject(sql, (rs, rowNum) -> {
UserOrderStats stats = new UserOrderStats();
stats.setOrderCount(rs.getInt("order_count"));
stats.setTotalAmount(rs.getBigDecimal("total_amount"));
stats.setAvgAmount(rs.getBigDecimal("avg_amount"));
stats.setCacheTime(new Date());
return stats;
}, userId);
}
/**
* 优化6:使用汇总表(定时任务维护)
*/
@Scheduled(cron = "0 0 1 * * ?") // 每天凌晨1点执行
public void updateDailySummary() {
String sql = "INSERT INTO t_order_daily_summary (summary_date, user_id, " +
"order_count, total_amount) " +
"SELECT DATE(create_time), user_id, COUNT(*), SUM(order_amount) " +
"FROM t_order " +
"WHERE create_time >= CURDATE() - INTERVAL 1 DAY " +
"AND create_time < CURDATE() " +
"GROUP BY DATE(create_time), user_id " +
"ON DUPLICATE KEY UPDATE " +
"order_count = VALUES(order_count), " +
"total_amount = VALUES(total_amount)";
int count = jdbcTemplate.update(sql);
log.info("Daily summary updated: {} records", count);
}
}
优化方案五:并行查询优化
@Service
@Slf4j
public class ParallelQueryService {
@Autowired
private TaskExecutor taskExecutor;
/**
* 优化7:分片并行查询
*/
public List<Order> parallelQueryByDateRange(Date startTime, Date endTime) {
// 按天分片
List<Date[]> dateSegments = splitDateRange(startTime, endTime, TimeUnit.DAYS.toMillis(1));
List<CompletableFuture<List<Order>>> futures = new ArrayList<>();
for (Date[] segment : dateSegments) {
CompletableFuture<List<Order>> future =
CompletableFuture.supplyAsync(() -> {
return queryOrdersByDate(segment[0], segment[1]);
}, taskExecutor);
futures.add(future);
}
// 合并结果
return futures.stream()
.map(CompletableFuture::join)
.flatMap(List::stream)
.collect(Collectors.toList());
}
private List<Order> queryOrdersByDate(Date start, Date end) {
String sql = "SELECT * FROM t_order WHERE create_time BETWEEN ? AND ? " +
"LIMIT 1000";
return jdbcTemplate.query(sql, new Object[]{start, end},
new BeanPropertyRowMapper<>(Order.class));
}
private List<Date[]> splitDateRange(Date start, Date end, long segmentMs) {
List<Date[]> segments = new ArrayList<>();
long startMs = start.getTime();
long endMs = end.getTime();
long current = startMs;
while (current < endMs) {
long next = Math.min(current + segmentMs, endMs);
segments.add(new Date[]{new Date(current), new Date(next)});
current = next;
}
return segments;
}
}
优化方案六:主从分离
@Configuration
public class DataSourceConfig {
// 读写分离配置
@Bean
@Primary
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource masterDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DataSource slaveDataSource() {
return DataSourceBuilder.create().build();
}
}
@Aspect
@Component
public class DataSourceRouter {
@Around("@annotation(readOnly)")
public Object route(ProceedingJoinPoint pjp, ReadOnly readOnly) {
if (readOnly.value()) {
// 只读操作路由到从库
DataSourceContextHolder.setSlave();
} else {
// 写操作路由到主库
DataSourceContextHolder.setMaster();
}
try {
return pjp.proceed();
} catch (Throwable e) {
throw new RuntimeException(e);
} finally {
DataSourceContextHolder.clear();
}
}
}
优化效果对比
@Test
public void performanceTest() {
// 优化前
long startTime = System.currentTimeMillis();
List<Order> orders = orderService.queryOrders("2023",
new Date(System.currentTimeMillis() - 86400000 * 30), new Date());
long cost = System.currentTimeMillis() - startTime;
System.out.println("优化前耗时: " + cost + "ms"); // 可能耗时 3000ms+
// 优化后
startTime = System.currentTimeMillis();
PageResult<Order> result = optimizedOrderService.queryOrdersByCursor(
null, 100, null, null);
cost = System.currentTimeMillis() - startTime;
System.out.println("优化后耗时: " + cost + "ms"); // < 50ms
}
| 优化维度 | 优化前 | 优化后 | 性能提升 |
|---|---|---|---|
| 查询方式 | 全表扫描 | 索引查询 | 10-100倍 |
| 分页方式 | OFFSET分页 | 游标分页 | 5-10倍 |
| 关联查询 | N+1查询 | 批量查询 | 大幅提升 |
| 统计查询 | 实时统计 | 缓存+汇总表 | 100倍以上 |
| 大数据量 | 单线程 | 并行分片 | 3-5倍 |
最佳实践建议
- 索引策略:根据查询模式创建合适的复合索引,遵循最左前缀原则
- 分页优化:避免使用OFFSET深分页,采用游标分页或延迟关联
- 批量操作:避免N+1查询,使用批量插入、批量更新
- 数据分区:对于超大表考虑使用分区表(按时间或范围分区)
- 读写分离:读多写少的场景使用主从复制
- 缓存策略:对热点数据和统计结果使用Redis缓存
- 异步处理:对非实时性要求不高的操作使用异步处理
- 监控告警:定期分析慢查询日志,使用EXPLAIN分析执行计划
通过以上优化方案,大表查询性能可以从秒级提升到毫秒级,满足业务高并发需求。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播