性能优化

本文档详细介绍 Unabo 框架的性能优化技巧和最佳实践。

目录


查询优化

1. 只查询需要的字段

// ❌ 不好:查询所有字段
List<User> users = bootstrap.queryTable("user").list();

// ✅ 好:只查询需要的字段
List<User> users = bootstrap.queryTable("user")
    .setFields("id", "name", "age")  // 只查询需要的字段
    .list(User.class);

// ✅ 使用 Map 接收,只查询部分字段
List<Map<String, Object>> users = bootstrap.queryTable("user")
    .setFields("id", "name")
    .maps();

TIP

只查询需要的字段可以:

  • 减少数据传输量
  • 降低内存占用
  • 提升查询速度

2. 使用索引字段作为查询条件

// ❌ 不好:使用非索引字段
List<User> users = bootstrap.queryTable("user")
    .addCondition(C.like("description", "%keyword%"))
    .list();

// ✅ 好:使用索引字段(id、name 等)
List<User> users = bootstrap.queryTable("user")
    .addCondition(C.eq("id", userId))  // 使用主键
    .list();

3. 避免使用 SELECT *

// ❌ 不好:SELECT *
List<User> users = bootstrap.queryTable("user")
    .maps();  // 默认查询所有字段

// ✅ 好:指定字段
List<User> users = bootstrap.queryTable("user")
    .setFields("id", "name", "email")  // 只查询需要的字段
    .maps();

4. 合理使用分页

// ❌ 不好:查询所有数据然后在内存中分页
List<User> allUsers = bootstrap.queryTable("user").list();
List<User> page1 = allUsers.subList(0, 10);

// ✅ 好:使用数据库分页
Page page = new Page(1, 10);
int total = bootstrap.queryTable("user").count();
page.setCount(total);

List<User> users = bootstrap.queryTable("user")
    .limit(page.skip(), 10)
    .sort(Sorts.DESC, "id")
    .list(User.class);

TIP

数据库分页 vs 内存分页:

  • 数据库分页:只查询当前页数据,性能好
  • 内存分页:查询所有数据,内存占用大,性能差

5. 避免在循环中查询

// ❌ 不好:在循环中查询(N+1 问题)
List<Order> orders = bootstrap.queryTable("order").list();
for (Order order : orders) {
    // 每次循环都查询一次数据库
    User user = bootstrap.queryTable("user")
        .addCondition(C.eq("id", order.getUserId()))
        .unique()
        .orElse(null);
    order.setUser(user);
}

// ✅ 好:批量查询
List<Order> orders = bootstrap.queryTable("order").list();
Set<Integer> userIds = orders.stream()
    .map(Order::getUserId)
    .collect(Collectors.toSet());

// 批量查询用户
List<User> users = bootstrap.queryTable("user")
    .addCondition(C.in("id", userIds))
    .list(User.class);

// 构建用户映射
Map<Integer, User> userMap = users.stream()
    .collect(Collectors.toMap(User::getId, u -> u));

// 设置用户
orders.forEach(order -> 
    order.setUser(userMap.get(order.getUserId()))
);

6. 使用流式处理大数据

// ❌ 不好:一次性加载所有数据到内存
List<User> users = bootstrap.queryTable("user").list();
for (User user : users) {
    processUser(user);
}

// ✅ 好:使用流式处理,分批处理
bootstrap.queryTable("user").stream(1000, rows -> {
    // rows 是一批数据,每批最多 1000 条
    for (Map<String, Object> row : rows) {
        User user = new User();
        user.setId((Integer) row.get("id"));
        user.setName((String) row.get("name"));
        processUser(user);
    }
});

或者使用 rowProcess 逐行处理:

// ✅ 逐行处理每条数据
bootstrap.queryTable("user").stream(1000,
    row -> {
        // 处理单行数据
        User user = new User();
        user.setId((Integer) row.get("id"));
        user.setName((String) row.get("name"));
        processUser(user);
    },
    rows -> {
        // 一批数据处理完成后的回调(可选)
        System.out.println("处理了一批 " + rows.size() + " 条数据");
    }
);

TIP

流式处理适用于:

  • 大数据量查询
  • 批量数据处理
  • 数据导出

流式处理的优势:

  • 减少内存占用
  • 支持百万级数据查询
  • 可自定义批次大小

批量操作优化

1. 批量插入

// ❌ 不好:循环单次插入
for (int i = 0; i < 1000; i++) {
    User user = new User();
    user.setName("用户" + i);
    bootstrap.query(user).insert();
}

// ✅ 好:批量插入
List<User> users = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
    User user = new User();
    user.setName("用户" + i);
    users.add(user);
}
bootstrap.query(users).insert();  // 批量插入

TIP

批量插入性能提升:

  • 单次插入:1000 次 SQL 执行
  • 批量插入:1 次 SQL 执行
  • 性能提升:100 倍以上

2. 批量更新

// ❌ 不好:循环单次更新
for (int i = 0; i < 1000; i++) {
    User user = new User();
    user.setId(i);
    user.setName("用户" + i);
    bootstrap.query(user).update();
}

// ✅ 好:批量更新
List<User> users = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
    User user = new User();
    user.setId(i);
    user.setName("用户" + i);
    users.add(user);
}
bootstrap.query(users).update();  // 批量更新

3. 合理设置批量大小

// ✅ 好:分批处理大数量
int batchSize = 500;  // 每批 500 条
List<User> allUsers = getAllUsers();  // 假设有 10000 条数据

for (int i = 0; i < allUsers.size(); i += batchSize) {
    int end = Math.min(i + batchSize, allUsers.size());
    List<User> batch = allUsers.subList(i, end);
    bootstrap.query(batch).insert();
}

TIP

批量大小建议:

  • MySQL:500-1000
  • PostgreSQL:500-1000
  • Oracle:100-500

缓存策略

1. 表字段缓存

Unabo 默认启用了表字段缓存,避免每次查询都查询数据库元数据。

// 配置缓存
Bootstrap bootstrap = Unabo.load(configuration -> {
    configuration.setUrl("jdbc:mysql://localhost:3306/test");
    configuration.setDriverOption(DriverOption.MYSQL_CJ);
    configuration.setUsername("root");
    configuration.setPassword("123456");
    // 表字段缓存默认开启
});

2. 查询结果缓存

WARNING

查询结果缓存默认关闭,因为可能导致脏数据。建议只在只读数据上开启。

// 开启查询结果缓存(谨慎使用)
Bootstrap bootstrap = Unabo.load(configuration -> {
    configuration.setUrl("jdbc:mysql://localhost:3306/test");
    configuration.setDriverOption(DriverOption.MYSQL_CJ);
    configuration.setUsername("root");
    configuration.setPassword("123456");
    configuration.setCache(true);  // 开启缓存
});

// 或者只缓存特定表
bootstrap.queryTable("sys_config")
    .enableCache(true)  // 只缓存配置表
    .maps();

3. 实体字段缓存

Unabo 自动缓存实体类的反射信息,提升性能。

// 实体字段缓存自动开启,无需配置
@Table(name = "user")
public class User {
    @Id
    private Integer id;
    private String name;
}

4. 手动刷新缓存

import online.sanen.unabo.sql.RuntimeCache;

// 刷新表字段缓存
RuntimeCache.removeTableCache(context);

// 刷新实体字段缓存(不建议手动操作)
// RuntimeCache.filedsCache.clear();

5. 缓存使用建议

// ✅ 好:对配置表、字典表等只读数据使用缓存
List<Map<String, Object>> config = bootstrap.queryTable("sys_config")
    .enableCache(true)
    .maps();

// ❌ 不好:对频繁更新的数据使用缓存
List<User> users = bootstrap.queryTable("user")
    .enableCache(true)  // 用户数据频繁更新,不适合缓存
    .maps();

连接池优化

1. 选择合适的连接池

// HikariCP(推荐)
Bootstrap bootstrap = Unabo.load(configuration -> {
    configuration.setDatasourceType(Configuration.DatasourceType.HikariCP);
});

// Druid
Bootstrap bootstrap = Unabo.load(configuration -> {
    configuration.setDatasourceType(Configuration.DatasourceType.Druid);
});

// Dbcp(默认)
Bootstrap bootstrap = Unabo.load(configuration -> {
    configuration.setDatasourceType(Configuration.DatasourceType.Dbcp);
});

TIP

连接池性能对比:

  • HikariCP:性能最好,推荐使用
  • Druid:功能丰富,有监控功能
  • Dbcp:Apache 基础连接池
  • C3P0:较老,不推荐

2. 配置连接池参数

Bootstrap bootstrap = Unabo.load(configuration -> {
    configuration.setUrl("jdbc:mysql://localhost:3306/test");
    configuration.setDriverOption(DriverOption.MYSQL_CJ);
    configuration.setUsername("root");
    configuration.setPassword("123456");
    configuration.setDatasourceType(Configuration.DatasourceType.HikariCP);
    
    // 连接池参数
    configuration.setMaxActive(50);      // 最大连接数
    configuration.setIsRemoveAbandoned(true);  // 移除被遗弃的连接
});

3. 获取连接池实例进行深度配置

import com.zaxxer.hikari.HikariDataSource;
import online.sanen.unabo.api.component.Manager;

Bootstrap bootstrap = Unabo.load(configuration -> {
    configuration.setDatasourceType(Configuration.DatasourceType.HikariCP);
});

// 获取连接池实例
Manager manager = bootstrap.manager();
HikariDataSource dataSource = (HikariDataSource) manager.getTemplate().getDataSource();

// 深度配置连接池
dataSource.setMaximumPoolSize(50);
dataSource.setMinimumIdle(10);
dataSource.setConnectionTimeout(30000);
dataSource.setIdleTimeout(600000);
dataSource.setMaxLifetime(1800000);
dataSource.setConnectionTestQuery("SELECT 1");

4. 连接池监控(Druid)

import com.alibaba.druid.pool.DruidDataSource;

Bootstrap bootstrap = Unabo.load(configuration -> {
    configuration.setDatasourceType(Configuration.DatasourceType.Druid);
});

// 获取 Druid 连接池
DruidDataSource dataSource = (DruidDataSource) bootstrap.manager()
    .getTemplate()
    .getDataSource();

// 开启监控
dataSource.setStatViewServletEnabled(true);
dataSource.setStatViewServletUrl("/druid");
dataSource.setStatViewServletLoginUsername("admin");
dataSource.setStatViewServletLoginPassword("123456");

// 访问 http://localhost:8080/druid 查看监控信息

索引优化

1. 创建索引

// 在数据库中创建索引
// CREATE INDEX idx_user_name ON user(name);
// CREATE INDEX idx_user_age ON user(age);
// CREATE INDEX idx_user_name_age ON user(name, age);

2. 使用索引字段查询

// ✅ 好:使用索引字段
List<User> users = bootstrap.queryTable("user")
    .addCondition(C.eq("name", "张三"))  // name 字段有索引
    .list();

// ✅ 好:使用复合索引
List<User> users = bootstrap.queryTable("user")
    .addCondition(C.eq("name", "张三"))
    .addCondition(C.gte("age", 18))  // name, age 有复合索引
    .list();

// ❌ 不好:使用非索引字段
List<User> users = bootstrap.queryTable("user")
    .addCondition(C.like("description", "%keyword%"))  // description 无索引
    .list();

3. 避免索引失效

// ❌ 不好:索引失效(函数操作)
List<User> users = bootstrap.queryTable("user")
    .addCondition(C.eq("SUBSTRING(name, 1, 1)", "张"))
    .list();

// ✅ 好:避免函数操作
List<User> users = bootstrap.queryTable("user")
    .addCondition(C.like("name", "张%"))
    .list();

// ❌ 不好:索引失效(类型转换)
List<User> users = bootstrap.queryTable("user")
    .addCondition(C.eq("age", "18"))  // age 是数字,但传字符串
    .list();

// ✅ 好:类型匹配
List<User> users = bootstrap.queryTable("user")
    .addCondition(C.eq("age", 18))
    .list();

4. 使用 EXPLAIN 分析查询

// 手动在数据库中执行 EXPLAIN
// EXPLAIN SELECT * FROM user WHERE name = '张三' AND age >= 18;

// 分析结果
// type: ref(使用了索引)
// key: idx_user_name_age(使用的索引)

监控与调优

1. 开启 SQL 日志

Bootstrap bootstrap = Unabo.load(configuration -> {
    configuration.setUrl("jdbc:mysql://localhost:3306/test");
    configuration.setDriverOption(DriverOption.MYSQL_CJ);
    configuration.setUsername("root");
    configuration.setPassword("123456");
    
    // 开启 SQL 日志
    configuration.setIsShowSql(true);  // 打印 SQL
    configuration.setFormat(true);    // 格式化 SQL
});

2. 慢查询日志

// 手动实现慢查询检测
public class SlowQueryMonitor {
    private static final long SLOW_QUERY_THRESHOLD = 1000; // 1秒
    
    public static List<User> queryUsers(Bootstrap bootstrap) {
        long startTime = System.currentTimeMillis();
        
        List<User> users = bootstrap.queryTable("user").list(User.class);
        
        long endTime = System.currentTimeMillis();
        long duration = endTime - startTime;
        
        if (duration > SLOW_QUERY_THRESHOLD) {
            System.err.println("慢查询警告:耗时 " + duration + "ms");
        }
        
        return users;
    }
}

3. 性能测试

public class PerformanceTest {
    
    public static void testInsertPerformance(Bootstrap bootstrap) {
        int count = 10000;
        
        // 测试单次插入
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < count; i++) {
            User user = new User();
            user.setName("用户" + i);
            bootstrap.query(user).insert();
        }
        long singleTime = System.currentTimeMillis() - startTime;
        System.out.println("单次插入 " + count + " 条,耗时:" + singleTime + "ms");
        
        // 测试批量插入
        List<User> users = new ArrayList<>();
        for (int i = 0; i < count; i++) {
            User user = new User();
            user.setName("用户" + i);
            users.add(user);
        }
        
        startTime = System.currentTimeMillis();
        bootstrap.query(users).insert();
        long batchTime = System.currentTimeMillis() - startTime;
        System.out.println("批量插入 " + count + " 条,耗时:" + batchTime + "ms");
        
        System.out.println("性能提升:" + (double) singleTime / batchTime + " 倍");
    }
}

4. 连接池监控

import com.zaxxer.hikari.HikariPoolMXBean;

public class PoolMonitor {
    
    public static void monitorPool(Bootstrap bootstrap) {
        HikariDataSource dataSource = (HikariDataSource) bootstrap.manager()
            .getTemplate()
            .getDataSource();
        
        HikariPoolMXBean pool = dataSource.getHikariPoolMXBean();
        
        System.out.println("活跃连接数:" + pool.getActiveConnections());
        System.out.println("空闲连接数:" + pool.getIdleConnections());
        System.out.println("总连接数:" + pool.getTotalConnections());
        System.out.println("等待线程数:" + pool.getThreadsAwaitingConnection());
    }
}

常见性能问题

问题1:N+1 查询问题

// ❌ 问题:N+1 查询
List<Order> orders = bootstrap.queryTable("order").list();
for (Order order : orders) {
    User user = bootstrap.queryTable("user")
        .addCondition(C.eq("id", order.getUserId()))
        .unique()
        .orElse(null);
}

// ✅ 解决:批量查询
List<Order> orders = bootstrap.queryTable("order").list();
Set<Integer> userIds = orders.stream()
    .map(Order::getUserId)
    .collect(Collectors.toSet());

List<User> users = bootstrap.queryTable("user")
    .addCondition(C.in("id", userIds))
    .list(User.class);

Map<Integer, User> userMap = users.stream()
    .collect(Collectors.toMap(User::getId, u -> u));

orders.forEach(order -> order.setUser(userMap.get(order.getUserId())));

问题2:大量数据一次性加载

// ❌ 问题:一次性加载大量数据
List<User> allUsers = bootstrap.queryTable("user").list();

// ✅ 解决:分页加载
Page page = new Page(1, 100);
int total = bootstrap.queryTable("user").count();
page.setCount(total);

List<User> users = bootstrap.queryTable("user")
    .limit(page.skip(), 100)
    .list(User.class);

// 或使用流式处理
bootstrap.queryTable("user").stream(100, rows -> {
    for (Map<String, Object> row : rows) {
        User user = new User();
        // 处理用户
    }
});

问题3:频繁创建 Bootstrap 实例

// ❌ 问题:频繁创建 Bootstrap 实例
public class UserService {
    public User getUser(int id) {
        Bootstrap bootstrap = Unabo.load(configuration -> {
            configuration.setUrl("jdbc:mysql://localhost:3306/test");
            // ...
        });
        return bootstrap.queryTable("user")
            .addCondition(C.eq("id", id))
            .unique(User.class)
            .orElse(null);
    }
}

// ✅ 解决:使用单例或依赖注入
public class UserService {
    
    @Resource
    private Bootstrap bootstrap;
    
    public User getUser(int id) {
        return bootstrap.queryTable("user")
            .addCondition(C.eq("id", id))
            .unique(User.class)
            .orElse(null);
    }
}

问题4:未使用索引

// ❌ 问题:查询未使用索引
List<User> users = bootstrap.queryTable("user")
    .addCondition(C.like("description", "%keyword%"))
    .list();

// ✅ 解决:添加索引或优化查询
// 添加索引:CREATE INDEX idx_user_description ON user(description);
// 或使用全文索引(MySQL)
// 或使用 Elasticsearch 等搜索引擎

问题5:长事务

// ❌ 问题:长事务
@Transactional
public void longTransaction() {
    // 查询
    List<User> users = bootstrap.queryTable("user").list();
    
    // 耗时操作
    for (User user : users) {
        // 耗时处理
        processUser(user);
    }
    
    // 更新
    bootstrap.query(users).update();
}

// ✅ 解决:缩小事务范围
public void optimizedTransaction() {
    // 查询(事务外)
    List<User> users = bootstrap.queryTable("user").list();
    
    // 耗时操作(事务外)
    for (User user : users) {
        processUser(user);
    }
    
    // 更新(事务内)
    updateUsersInTransaction(users);
}

@Transactional
public void updateUsersInTransaction(List<User> users) {
    bootstrap.query(users).update();
}

完整示例

import online.sanen.unabo.api.Bootstrap;
import online.sanen.unabo.api.condition.C;
import online.sanen.unabo.api.structure.enums.DriverOption;
import online.sanen.unabo.api.structure.enums.Sorts;
import online.sanen.unabo.sql.factory.Unabo;
import com.zaxxer.hikari.HikariDataSource;
import java.util.*;

public class PerformanceOptimizationExample {
    
    public static void main(String[] args) {
        // 创建优化的 Bootstrap 实例
        try (Bootstrap bootstrap = createOptimizedBootstrap()) {
            
            // 1. 批量插入性能测试
            testBatchInsert(bootstrap);
            
            // 2. 分页查询性能测试
            testPaginationQuery(bootstrap);
            
            // 3. 批量查询优化示例
            batchQueryOptimization(bootstrap);
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    /**
     * 创建优化的 Bootstrap 实例
     */
    private static Bootstrap createOptimizedBootstrap() {
        Bootstrap bootstrap = Unabo.load("optimized", configuration -> {
            configuration.setUrl("jdbc:mysql://localhost:3306/test");
            configuration.setDriverOption(DriverOption.MYSQL_CJ);
            configuration.setUsername("root");
            configuration.setPassword("123456");
            
            // 使用 HikariCP 连接池
            configuration.setDatasourceType(Configuration.DatasourceType.HikariCP);
            configuration.setMaxActive(50);
            configuration.setIsRemoveAbandoned(true);
            
            // 开启 SQL 日志(生产环境建议关闭)
            configuration.setIsShowSql(true);
        });
        
        // 深度配置连接池
        HikariDataSource dataSource = (HikariDataSource) bootstrap.manager()
            .getTemplate()
            .getDataSource();
        
        dataSource.setMaximumPoolSize(50);
        dataSource.setMinimumIdle(10);
        dataSource.setConnectionTimeout(30000);
        
        return bootstrap;
    }
    
    /**
     * 批量插入性能测试
     */
    private static void testBatchInsert(Bootstrap bootstrap) {
        int count = 10000;
        
        // 单次插入
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < count; i++) {
            Map<String, Object> user = new HashMap<>();
            user.put("name", "用户" + i);
            user.put("age", 20 + i % 40);
            bootstrap.queryMap("user", user).insert();
        }
        long singleTime = System.currentTimeMillis() - startTime;
        System.out.println("单次插入 " + count + " 条,耗时:" + singleTime + "ms");
        
        // 批量插入
        List<Map<String, Object>> users = new ArrayList<>();
        for (int i = 0; i < count; i++) {
            Map<String, Object> user = new HashMap<>();
            user.put("name", "用户" + i);
            user.put("age", 20 + i % 40);
            users.add(user);
        }
        
        startTime = System.currentTimeMillis();
        bootstrap.queryMap("user", users).insert();
        long batchTime = System.currentTimeMillis() - startTime;
        System.out.println("批量插入 " + count + " 条,耗时:" + batchTime + "ms");
        System.out.println("性能提升:" + (double) singleTime / batchTime + " 倍");
    }
    
    /**
     * 分页查询性能测试
     */
    private static void testPaginationQuery(Bootstrap bootstrap) {
        int pageSize = 100;
        int currentPage = 1;
        
        long startTime = System.currentTimeMillis();
        
        // 查询总数
        int total = bootstrap.queryTable("user").count();
        
        // 创建分页对象
        com.mhdt.structure.Page page = new com.mhdt.structure.Page(currentPage, pageSize);
        page.setCount(total);
        
        // 分页查询
        List<Map<String, Object>> users = bootstrap.queryTable("user")
            .limit(page.skip(), pageSize)
            .sort(Sorts.DESC, "id")
            .maps();
        
        long duration = System.currentTimeMillis() - startTime;
        
        System.out.println("分页查询:页数 " + currentPage + ",每页 " + pageSize + " 条,耗时:" + duration + "ms");
        System.out.println("总记录数:" + total + ",总页数:" + page.getTotalPage());
    }
    
    /**
     * 批量查询优化示例
     */
    private static void batchQueryOptimization(Bootstrap bootstrap) {
        // 查询订单列表
        List<Map<String, Object>> orders = bootstrap.queryTable("order")
            .limit(0, 100)
            .maps();
        
        // 提取用户 ID
        Set<Integer> userIds = orders.stream()
            .map(order -> (Integer) order.get("user_id"))
            .collect(Collectors.toSet());
        
        // 批量查询用户
        List<Map<String, Object>> users = bootstrap.queryTable("user")
            .addCondition(C.in("id", userIds))
            .maps();
        
        // 构建用户映射
        Map<Integer, Map<String, Object>> userMap = users.stream()
            .collect(Collectors.toMap(
                user -> (Integer) user.get("id"),
                user -> user
            ));
        
        // 设置用户信息
        long startTime = System.currentTimeMillis();
        orders.forEach(order -> {
            Integer userId = (Integer) order.get("user_id");
            Map<String, Object> user = userMap.get(userId);
            order.put("user_name", user.get("name"));
            order.put("user_email", user.get("email"));
        });
        long duration = System.currentTimeMillis() - startTime;
        
        System.out.println("批量查询优化:处理 " + orders.size() + " 条订单,耗时:" + duration + "ms");
    }
}

下一步