五、数据库连接池调优
5.1 HikariCP配置详解(当前性能最强)
spring:
datasource:
hikari:
# 核心配置
minimum-idle: 10 # 最小空闲连接数
maximum-pool-size: 20 # 最大连接池大小(公式:((core_count * 2) + effective_spindle_count))
connection-timeout: 30000 # 连接超时(ms)
idle-timeout: 600000 # 空闲超时(10分钟)
max-lifetime: 1800000 # 连接最大生命周期(30分钟)
# 性能优化配置
connection-test-query: SELECT 1
validation-timeout: 5000
leak-detection-threshold: 60000 # 连接泄漏检测(60秒)
# MySQL特定配置
data-source-properties:
cachePrepStmts: true
prepStmtCacheSize: 250
prepStmtCacheSqlLimit: 2048
useServerPrepStmts: true
useLocalSessionState: true
rewriteBatchedStatements: true
cacheResultSetMetadata: true
cacheServerConfiguration: true
elideSetAutoCommits: true
maintainTimeStats: false
5.2 连接池大小公式
Tomcat JDBC / HikariCP 通用公式:
实际建议:
单机部署:最大连接数 = CPU核心数 × 2
微服务场景:单个服务连接数不宜超过50
// 动态调整连接池(根据负载)
@Component
public class DynamicDataSourceConfig {
@EventListener
public void handleSlowQuery(SlowQueryEvent event) {
HikariPoolMXBean poolMXBean = getPoolMXBean();
int activeConnections = poolMXBean.getActiveConnections();
int maxConnections = poolMXBean.getMaximumPoolSize();
if (activeConnections > maxConnections * 0.8) {
// 连接使用率超过80%,扩容
poolMXBean.setMaximumPoolSize(maxConnections + 5);
log.warn("连接池扩容至: {}", maxConnections + 5);
}
}
}
5.3 连接泄漏检测
// 配置泄漏检测(生产环境建议开启)
@Configuration
public class DataSourceConfig {
@Bean
public DataSource dataSource() {
HikariConfig config = new HikariConfig();
config.setLeakDetectionThreshold(30000); // 30秒未归还即告警
config.setConnectionTimeout(30000);
return new HikariDataSource(config);
}
}
// 监控连接状态
@Scheduled(fixedDelay = 60000)
public void monitorConnections() {
HikariPoolMXBean poolMXBean = getPoolMXBean();
logger.info("连接池状态: 活跃={}, 空闲={}, 总数={}, 等待线程={}",
poolMXBean.getActiveConnections(),
poolMXBean.getIdleConnections(),
poolMXBean.getTotalConnections(),
poolMXBean.getThreadsAwaitingConnection());
}
六、事务隔离级别与锁机制
6.1 四种隔离级别对比
6.2 MVCC原理(多版本并发控制)
-- InnoDB每行数据隐藏列:
-- DB_TRX_ID: 最后修改的事务ID
-- DB_ROLL_PTR: 回滚指针(指向undo log)
-- DB_ROW_ID: 行ID(聚簇索引无此列)
-- READ COMMITTED下:每次查询生成新的ReadView
-- REPEATABLE READ下:事务内第一次查询生成ReadView,后续复用
-- 查看当前事务ID
SELECT TRX_ID FROM information_schema.innodb_trx WHERE TRX_MYSQL_THREAD_ID = CONNECTION_ID();
6.3 锁的类型与使用场景
-- 行锁(InnoDB默认)
-- 注意:只有通过索引查询才会使用行锁,否则升级为表锁
-- 共享锁(S锁):允许其他事务读,不允许写
SELECT * FROM orders WHERE id = 1 LOCK IN SHARE MODE;
-- 排他锁(X锁):不允许其他事务读和写
SELECT * FROM orders WHERE id = 1 FOR UPDATE;
-- 间隙锁(Gap Lock):锁定范围,防止幻读(仅在REPEATABLE READ级别)
-- 假设orders表id有1,2,5,10
SELECT * FROM orders WHERE id BETWEEN 3 AND 7 FOR UPDATE;
-- 锁定范围:(2,5)、(5,10)
-- 临键锁(Next-Key Lock)= 行锁 + 间隙锁
-- 死锁示例与排查
-- 事务A:
BEGIN;
UPDATE orders SET status = 1 WHERE id = 1;
UPDATE orders SET status = 2 WHERE id = 2;
COMMIT;
-- 事务B:
BEGIN;
UPDATE orders SET status = 3 WHERE id = 2;
UPDATE orders SET status = 4 WHERE id = 1;
COMMIT;
-- 可能死锁
-- 查看死锁信息
SHOW ENGINE INNODB STATUS\G
-- 死锁日志位置:/var/log/mysql/error.log
-- 避免死锁策略:
-- 1. 固定访问顺序(按主键排序)
-- 2. 缩短事务时间
-- 3. 使用低隔离级别
-- 4. 添加合适的索引
6.4 事务最佳实践
@Service
@Transactional(rollbackFor = Exception.class)
public class OrderService {
// 正确做法:事务边界清晰,粒度适中
public void createOrder(OrderDTO dto) {
// 1. 校验(非数据库操作,放在事务外)
validateOrder(dto);
// 2. 核心业务(事务内)
Order order = new Order();
order.setUserId(dto.getUserId());
order.setAmount(dto.getAmount());
orderMapper.insert(order);
// 3. 扣库存(分布式事务场景考虑最终一致性)
inventoryService.decreaseStock(dto.getProductId(), dto.getQuantity());
// 4. 发送消息(事务内无法保证消息可靠,使用事务消息)
eventPublisher.publish(new OrderCreatedEvent(order.getId()));
}
// 错误做法1:事务过大(包含RPC、文件操作)
@Transactional
public void badMethod() {
orderMapper.insert(order);
restTemplate.postForObject("http://payment/pay", order, Void.class); // RPC
fileService.writeToFile(order); // 文件IO
// 问题:事务期间持有数据库连接,RPC/IO阻塞会导致连接长时间占用
}
// 错误做法2:捕获异常不抛出
@Transactional
public void badMethod2() {
try {
orderMapper.insert(order);
int i = 1 / 0; // 抛出异常
} catch (Exception e) {
// 捕获后不抛出,事务不会回滚!
log.error("error", e);
}
}
// 错误做法3:事务传播级别使用不当
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void createLog(Log log) {
// 独立事务,不受外层事务影响
logMapper.insert(log);
}
}
6.5 事务传播级别详解
// NESTED的使用场景:部分回滚
@Transactional
public void parentMethod() {
orderMapper.insert(order1);
try {
childMethod(); // 失败只回滚childMethod内的操作
} catch (Exception e) {
// childMethod已回滚,parentMethod继续
}
orderMapper.insert(order2);
}
@Transactional(propagation = Propagation.NESTED)
public void childMethod() {
// 嵌套事务,内部失败只回滚到保存点
}