Java关联查询提速案例怎么做

wen java案例 27

本文目录导读:

Java关联查询提速案例怎么做

  1. 方案一:SQL级联 + 索引优化(最推荐,改SQL即可)
  2. 方案二:IN子查询 + 分批/缓存(适合多表且JOIN太慢)
  3. 方案三:缓存层(Redis/本地缓存,高频查询)
  4. 方案四:宽表 + 数据冗余(终极但需权衡)
  5. 核心加速对照表
  6. 给你一个最落地的建议

关于Java关联查询提速,我给你一个从最落地到最复杂的实战案例全攻略,这里假设你用的是MySQL + MyBatis,这是最常见的组合。

核心思路:减少数据库连接数 + 利用索引 + 避免N+1查询。


场景假设

  • 表A:订单表 (order) — 100万数据
  • 表B:用户表 (user) — 10万数据
  • 需求:查询最近1000笔订单,并带上用户名称。

原始慢查询(反面案例)

// UserMapper
User selectById(Long userId);
// OrderService
for (Order order : orderList) {
    User user = userMapper.selectById(order.getUserId()); // 循环查数据库!
    order.setUserName(user.getName());
}

问题:N+1查询,1000笔订单 -> 1001次数据库查询。


SQL级联 + 索引优化(最推荐,改SQL即可)

原理:让数据库一次JOIN出结果,减少应用层循环。

写JOIN SQL

<select id="selectOrdersWithUser" resultMap="orderUserMap">
    SELECT o.*, u.id as u_id, u.name as u_name
    FROM `order` o
    LEFT JOIN `user` u ON o.user_id = u.id
    WHERE o.create_time > #{startTime}
    ORDER BY o.create_time DESC
    LIMIT 1000
</select>

必加索引(性能关键)

-- 订单表:user_id字段加索引(用于JOIN)
ALTER TABLE `order` ADD INDEX idx_user_id (`user_id`);
-- 用户表:id字段已是主键索引,不用加
-- 订单表:按时间排序时,给create_time加索引(避免文件排序)
ALTER TABLE `order` ADD INDEX idx_create_time (`create_time`);

结果:1次查询搞定,索引命中后性能提升10-100倍。


IN子查询 + 分批/缓存(适合多表且JOIN太慢)

当JOIN太复杂(多对多,或表非常大)时,用先查主表,再批量查关联表

// 1. 先只查主表(只查询1000条订单,速度快)
List<Order> orders = orderMapper.selectRecentOrders(1000);
// 2. 提取所有userId,去重
Set<Long> userIds = orders.stream()
    .map(Order::getUserId)
    .collect(Collectors.toSet());
// 3. IN查询一次拿到所有用户(批量查询)
List<User> users = userMapper.selectBatchByIds(new ArrayList<>(userIds));
// 4. 构建Map,内存中关联
Map<Long, User> userMap = users.stream()
    .collect(Collectors.toMap(User::getId, u -> u));
// 5. 赋值
orders.forEach(o -> o.setUserName(userMap.get(o.getUserId()).getName()));

对比

  • 原始:1001次查询
  • 优化后:2次查询(订单查1次 + 用户查1次)

适用场景:表非常大,JOIN导致临时表太大、内存不足。


缓存层(Redis/本地缓存,高频查询)

适合:用户表几乎不常变,但被频繁关联。

@Service
public class UserService {
    @Autowired
    private StringRedisTemplate redisTemplate;
    public User getById(Long id) {
        // 1. 先从缓存查
        String json = redisTemplate.opsForValue().get("user:" + id);
        if (json != null) {
            return JSON.parseObject(json, User.class);
        }
        // 2. 缓存没有,查数据库
        User user = userMapper.selectById(id);
        // 3. 写入缓存(设置过期时间)
        redisTemplate.opsForValue().set("user:" + id, JSON.toJSONString(user), 3600, TimeUnit.SECONDS);
        return user;
    }
}

效果:第二次查询直接走内存,毫秒级。


宽表 + 数据冗余(终极但需权衡)

原理:把关联字段直接冗余到主表中,避免关联。

-- 订单表增加 user_name 字段
ALTER TABLE `order` ADD COLUMN user_name VARCHAR(100);
-- 插入/更新时同步(异步或触发器)
UPDATE `order` o JOIN `user` u ON o.user_id = u.id
SET o.user_name = u.name;

优点:查询直接 SELECT * FROM order,零关联。 缺点

  • 数据冗余,更新用户名称时需要同步更新所有订单。
  • 适合写少读多的场景(如日志、报表)。

核心加速对照表

方案 机制 适用场景 性能提升
SQL JOIN + 索引 数据库一次关联 两表大小适中,关联字段有索引 10-50倍
IN子查询 + 分批 减少数据库连接数 大表关联或多对多 5-10倍
缓存层 减少数据库查询 高频查询、低频更新的关联表 100-1000倍
宽表/冗余 空间换时间 读多写极少、报表统计 10-30倍

给你一个最落地的建议

第一步:先检查索引(EXPLAIN你的查询,看type是不是refindex,有无Using filesort)。

第二步:如果业务简单,直接改SQL为JOIN(方案一),一般是性价比最高的。

第三步:如果JOIN后还是慢(超过1秒),换成方案二(IN分批查询),并用MyBatis的@MapKey直接构建Map。

第四步:如果还是高频,加Redis缓存(方案三)。

不要一开始就上宽表或分库分表,容易过度设计。

抱歉,评论功能暂时关闭!