性能优化
本文档详细介绍 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");
}
}