MySQL 性能优化
1. 索引优化
1.1 索引设计原则
1.1.1 选择性高的字段
sql
-- ✅ 好的索引:选择性高
CREATE INDEX idx_username ON system_users(username);
CREATE INDEX idx_mobile ON system_users(mobile);
-- ❌ 差的索引:选择性低
CREATE INDEX idx_sex ON system_users(sex); -- 性别只有两个值,选择性低1.1.2 联合索引最左前缀原则
sql
-- 创建联合索引
CREATE INDEX idx_user_type_status ON system_users(user_type, status);
-- ✅ 可以使用索引
SELECT * FROM system_users WHERE user_type = 1;
SELECT * FROM system_users WHERE user_type = 1 AND status = 0;
-- ❌ 无法使用索引
SELECT * FROM system_users WHERE status = 0;1.1.3 覆盖索引
sql
-- 创建覆盖索引
CREATE INDEX idx_user_dept_status ON system_users(dept_id, status, username);
-- ✅ 查询只需要索引中的字段,不需要回表
SELECT username FROM system_users WHERE dept_id = 1 AND status = 0;1.2 索引使用规范
| 场景 | 索引类型 | 示例 |
|---|---|---|
| 主键查询 | 主键索引 | WHERE id = 1 |
| 唯一性查询 | 唯一索引 | WHERE username = 'admin' |
| 范围查询 | 普通索引 | WHERE create_time > '2024-01-01' |
| 排序查询 | 联合索引 | ORDER BY create_time DESC |
| 多条件查询 | 联合索引 | WHERE type = 1 AND status = 0 |
| 模糊查询 | 前缀索引 | WHERE username LIKE 'admin%' |
1.3 索引优化技巧
1.3.1 避免索引失效
sql
-- ❌ 索引失效的情况
SELECT * FROM system_users WHERE username LIKE '%admin%';
SELECT * FROM system_users WHERE YEAR(create_time) = 2024;
SELECT * FROM system_users WHERE username + 1 = 100;
-- ✅ 正确的使用方式
SELECT * FROM system_users WHERE username LIKE 'admin%';
SELECT * FROM system_users WHERE create_time >= '2024-01-01' AND create_time < '2025-01-01';
SELECT * FROM system_users WHERE username = 100;1.3.2 使用 EXPLAIN 分析查询
sql
-- 分析查询执行计划
EXPLAIN SELECT * FROM system_users WHERE username = 'admin';
-- 查看索引使用情况
SHOW INDEX FROM system_users;
-- 查看索引统计信息
SHOW INDEX STATUS FROM system_users;1.4 索引维护
sql
-- 分析表,更新索引统计信息
ANALYZE TABLE system_users;
-- 优化表,回收空间
OPTIMIZE TABLE system_users;
-- 检查表
CHECK TABLE system_users;
-- 修复表
REPAIR TABLE system_users;2. 查询优化
2.1 查询优化原则
2.1.1 避免 SELECT *
sql
-- ❌ 查询所有字段
SELECT * FROM system_users WHERE dept_id = 1;
-- ✅ 只查询需要的字段
SELECT id, username, nickname FROM system_users WHERE dept_id = 1;2.1.2 避免子查询
sql
-- ❌ 使用子查询
SELECT * FROM system_users WHERE dept_id IN (SELECT id FROM system_dept WHERE status = 0);
-- ✅ 使用 JOIN
SELECT u.* FROM system_users u
INNER JOIN system_dept d ON u.dept_id = d.id
WHERE d.status = 0;2.1.3 合理使用 LIMIT
sql
-- ✅ 分页查询
SELECT * FROM system_users WHERE dept_id = 1 ORDER BY id LIMIT 10, 10;
-- ✅ 使用游标分页(性能更好)
SELECT * FROM system_users WHERE dept_id = 1 AND id > 100 ORDER BY id LIMIT 10;2.2 MyBatis Plus 查询优化
2.2.1 使用 LambdaQueryWrapperX
java
// ✅ 使用 Lambda 查询,避免硬编码字段名
new LambdaQueryWrapperX<AdminUserDO>()
.eq(AdminUserDO::getDeptId, deptId)
.eq(AdminUserDO::getStatus, status)
.like(AdminUserDO::getUsername, username)
.orderByDesc(AdminUserDO::getId);2.2.2 分页查询优化
java
// ✅ 使用分页插件
PageResult<AdminUserDO> pageResult = userMapper.selectPage(reqVO,
new LambdaQueryWrapperX<AdminUserDO>()
.eq(AdminUserDO::getDeptId, deptId)
.orderByDesc(AdminUserDO::getId));
// 配置分页插件
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}2.2.3 批量查询优化
java
// ✅ 使用 IN 查询
List<AdminUserDO> users = userMapper.selectList(
new LambdaQueryWrapperX<AdminUserDO>()
.in(AdminUserDO::getId, userIds)
);
// ✅ 使用批量查询
Map<Long, AdminUserDO> userMap = userMapper.selectList(
new LambdaQueryWrapperX<AdminUserDO>()
.in(AdminUserDO::getId, userIds)
).stream().collect(Collectors.toMap(AdminUserDO::getId, Function.identity()));2.3 慢查询优化
2.3.1 开启慢查询日志
yaml
# application.yaml
spring:
datasource:
url: jdbc:mysql://localhost:3306/ruoyi-vue-pro?useUnicode=true&characterEncoding=UTF8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true
driver-class-name: com.mysql.cj.jdbc.Driversql
-- 开启慢查询日志
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;
SET GLOBAL slow_query_log_file = '/var/log/mysql/mysql-slow.log';
-- 查看慢查询配置
SHOW VARIABLES LIKE '%slow_query%';2.3.2 分析慢查询
sql
-- 使用 mysqldumpslow 分析慢查询
mysqldumpslow -s t -t 10 /var/log/mysql/mysql-slow.log
-- 使用 pt-query-digest 分析慢查询
pt-query-digest /var/log/mysql/mysql-slow.log3. 表结构优化
3.1 数据类型选择
| 数据类型 | 存储空间 | 使用场景 | 优化建议 |
|---|---|---|---|
| TINYINT | 1字节 | 小范围整数 | 状态、类型 |
| SMALLINT | 2字节 | 中等范围整数 | 数量、排序 |
| INT | 4字节 | 大范围整数 | ID、数量 |
| BIGINT | 8字节 | 超大范围整数 | 主键、外键 |
| DECIMAL(M,D) | M+2字节 | 精确数值 | 金额、价格 |
| VARCHAR(N) | N+1或N+2字节 | 变长字符串 | 用户名、邮箱 |
| CHAR(N) | N字节 | 定长字符串 | 固定编码 |
| TEXT | 65535字节 | 长文本 | 内容、描述 |
| JSON | 实际长度 | JSON数据 | 扩展信息 |
3.2 字段优化
sql
-- ✅ 使用合适的数据类型
`status` tinyint NOT NULL DEFAULT '0' COMMENT '状态',
`price` decimal(10,2) DEFAULT NULL COMMENT '价格',
`username` varchar(50) NOT NULL COMMENT '用户名',
-- ❌ 不合适的数据类型
`status` int NOT NULL DEFAULT '0' COMMENT '状态', -- 浪费空间
`price` varchar(20) DEFAULT NULL COMMENT '价格', -- 精度丢失
`username` varchar(255) NOT NULL COMMENT '用户名', -- 浪费空间3.3 表分区
sql
-- 按时间范围分区
CREATE TABLE `system_log` (
`id` bigint NOT NULL AUTO_INCREMENT,
`content` text,
`create_time` datetime NOT NULL,
PRIMARY KEY (`id`, `create_time`)
) ENGINE=InnoDB
PARTITION BY RANGE (YEAR(create_time)) (
PARTITION p2023 VALUES LESS THAN (2024),
PARTITION p2024 VALUES LESS THAN (2025),
PARTITION p2025 VALUES LESS THAN (2026),
PARTITION pmax VALUES LESS THAN MAXVALUE
);3.4 表分库分表
对于大表(超过1000万行),考虑分库分表:
java
// 使用 Sharding-JDBC 进行分库分表
@Configuration
public class ShardingConfig {
@Bean
public DataSource shardingDataSource() throws SQLException {
ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
// 配置分表规则
TableRuleConfiguration tableRuleConfig = new TableRuleConfiguration();
tableRuleConfig.setLogicTable("system_users");
tableRuleConfig.setActualDataNodes("ds${0..1}.system_users_${0..1}");
// 配置分库策略
tableRuleConfig.setDatabaseShardingStrategyConfig(
new StandardShardingStrategyConfiguration("id", "modShardingAlgorithm"));
// 配置分表策略
tableRuleConfig.setTableShardingStrategyConfig(
new StandardShardingStrategyConfiguration("id", "modShardingAlgorithm"));
shardingRuleConfig.getTableRuleConfigs().add(tableRuleConfig);
return ShardingDataSourceFactory.createDataSource(createDataSourceMap(), shardingRuleConfig);
}
}4. 缓存优化
4.1 Redis 缓存配置
yaml
# application.yaml
spring:
cache:
type: REDIS
redis:
time-to-live: 1h # 设置过期时间为 1 小时
data:
redis:
repositories:
enabled: false # 禁用 Spring Data Redis 的 Repository4.2 缓存使用规范
4.2.1 使用 Spring Cache
java
@Service
public class UserServiceImpl implements UserService {
@Cacheable(cacheNames = "user", key = "#id")
public UserRespVO getUser(Long id) {
return userMapper.selectById(id);
}
@CacheEvict(cacheNames = "user", key = "#id")
public void updateUser(UserSaveReqVO reqVO) {
userMapper.updateById(convert(reqVO));
}
@CachePut(cacheNames = "user", key = "#result.id")
public UserRespVO createUser(UserSaveReqVO reqVO) {
userMapper.insert(convert(reqVO));
return convert(reqVO);
}
}4.2.2 缓存穿透
java
// 使用布隆过滤器防止缓存穿透
@Service
public class UserServiceImpl implements UserService {
@Resource
private RedissonClient redissonClient;
public UserRespVO getUser(Long id) {
// 检查布隆过滤器
RBloomFilter<Long> bloomFilter = redissonClient.getBloomFilter("userBloomFilter");
if (!bloomFilter.contains(id)) {
return null;
}
// 查询缓存
String cacheKey = "user:" + id;
UserRespVO user = redisTemplate.opsForValue().get(cacheKey);
if (user != null) {
return user;
}
// 查询数据库
user = userMapper.selectById(id);
if (user != null) {
redisTemplate.opsForValue().set(cacheKey, user, 1, TimeUnit.HOURS);
} else {
// 缓存空值,防止缓存穿透
redisTemplate.opsForValue().set(cacheKey, NULL_VALUE, 5, TimeUnit.MINUTES);
}
return user;
}
}4.2.3 缓存雪崩
java
// 设置随机过期时间,防止缓存雪崩
@Service
public class UserServiceImpl implements UserService {
public UserRespVO getUser(Long id) {
String cacheKey = "user:" + id;
UserRespVO user = redisTemplate.opsForValue().get(cacheKey);
if (user != null) {
return user;
}
user = userMapper.selectById(id);
if (user != null) {
// 设置随机过期时间(1小时 + 随机0-10分钟)
long expireTime = 60 + ThreadLocalRandom.current().nextInt(0, 10);
redisTemplate.opsForValue().set(cacheKey, user, expireTime, TimeUnit.MINUTES);
}
return user;
}
}4.2.4 缓存击穿
java
// 使用互斥锁防止缓存击穿
@Service
public class UserServiceImpl implements UserService {
@Resource
private RedissonClient redissonClient;
public UserRespVO getUser(Long id) {
String cacheKey = "user:" + id;
UserRespVO user = redisTemplate.opsForValue().get(cacheKey);
if (user != null) {
return user;
}
// 使用分布式锁
RLock lock = redissonClient.getLock("lock:user:" + id);
try {
if (lock.tryLock(10, TimeUnit.SECONDS)) {
// 双重检查
user = redisTemplate.opsForValue().get(cacheKey);
if (user != null) {
return user;
}
// 查询数据库
user = userMapper.selectById(id);
if (user != null) {
redisTemplate.opsForValue().set(cacheKey, user, 1, TimeUnit.HOURS);
}
}
} finally {
lock.unlock();
}
return user;
}
}5. 批量操作优化
5.1 批量插入
java
// ✅ 使用批量插入
@Service
public class UserServiceImpl implements UserService {
public void batchInsertUsers(List<UserSaveReqVO> reqVOList) {
List<AdminUserDO> users = convertList(reqVOList);
userMapper.insertBatch(users);
}
}5.2 批量更新
java
// ✅ 使用批量更新
@Service
public class UserServiceImpl implements UserService {
public void batchUpdateUsers(List<UserSaveReqVO> reqVOList) {
List<AdminUserDO> users = convertList(reqVOList);
userMapper.updateBatch(users);
}
}5.3 批量删除
java
// ✅ 使用批量删除
@Service
public class UserServiceImpl implements UserService {
public void batchDeleteUsers(List<Long> ids) {
userMapper.deleteBatchIds(ids);
}
}6. 事务优化
6.1 事务范围控制
java
// ✅ 缩小事务范围
@Service
public class UserServiceImpl implements UserService {
@Transactional(rollbackFor = Exception.class)
public void createUser(UserSaveReqVO reqVO) {
// 事务开始
AdminUserDO user = convert(reqVO);
userMapper.insert(user);
// 非事务操作
sendNotification(user);
// 事务结束
}
}6.2 事务传播行为
java
// ✅ 使用 REQUIRES_NEW 创建新事务
@Service
public class UserServiceImpl implements UserService {
@Transactional(rollbackFor = Exception.class)
public void bigTransaction() {
methodA();
methodB();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodA() {
// 新事务
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
// 新事务
}
}6.3 避免长事务
java
// ❌ 长事务
@Transactional(rollbackFor = Exception.class)
public void longTransaction() {
List<AdminUserDO> users = userMapper.selectList(null);
for (AdminUserDO user : users) {
processUser(user); // 处理每个用户
}
}
// ✅ 拆分为短事务
public void shortTransaction() {
List<AdminUserDO> users = userMapper.selectList(null);
for (AdminUserDO user : users) {
processUserInTransaction(user);
}
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void processUserInTransaction(AdminUserDO user) {
processUser(user);
}7. 连接池配置
7.1 HikariCP 配置
yaml
# application.yaml
spring:
datasource:
url: jdbc:mysql://localhost:3306/ruoyi-vue-pro?useUnicode=true&characterEncoding=UTF8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
hikari:
# 连接池配置
minimum-idle: 5 # 最小空闲连接数
maximum-pool-size: 20 # 最大连接数
connection-timeout: 30000 # 连接超时时间(毫秒)
idle-timeout: 600000 # 空闲连接超时时间(毫秒)
max-lifetime: 1800000 # 连接最大生命周期(毫秒)
connection-test-query: SELECT 1 # 连接测试查询
# 性能优化
pool-name: FanyiHikariCP # 连接池名称
auto-commit: true # 自动提交
read-only: false # 只读模式7.2 连接池监控
java
@Configuration
public class HikariConfig {
@Bean
public DataSource dataSource() {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/ruoyi-vue-pro");
dataSource.setUsername("root");
dataSource.setPassword("root");
dataSource.setMinimumIdle(5);
dataSource.setMaximumPoolSize(20);
// 注册 JMX 监控
dataSource.setMetricRegistry(metricRegistry);
return dataSource;
}
}8. MyBatis Plus 性能优化
8.1 配置优化
yaml
# application.yaml
mybatis-plus:
configuration:
map-underscore-to-camel-case: true # 驼峰命名转换
cache-enabled: true # 开启二级缓存
lazy-loading-enabled: true # 开启延迟加载
aggressive-lazy-loading: false # 按需加载
default-executor-type: REUSE # 使用重用预处理语句
default-statement-timeout: 30 # 查询超时时间(秒)
global-config:
db-config:
id-type: NONE # 智能模式
logic-delete-value: 1 # 逻辑删除值
logic-not-delete-value: 0 # 逻辑未删除值
banner: false # 关闭 Banner
type-aliases-package: ${fanyi.info.base-package}.dal.dataobject8.2 拦截器优化
java
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 分页插件
PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor();
paginationInterceptor.setMaxLimit(1000); // 最大单页限制数量
paginationInterceptor.setOverflow(false); // 溢出总页数后是否进行处理
interceptor.addInnerInterceptor(paginationInterceptor);
// 动态表名插件
for (TableNameHandler h : ServiceLoader.load(TableNameHandler.class)) {
DynamicTableNameInnerInterceptor dynamicTableNameInnerInterceptor = new DynamicTableNameInnerInterceptor();
dynamicTableNameInnerInterceptor.setTableNameHandler(h);
interceptor.addInnerInterceptor(dynamicTableNameInnerInterceptor);
}
return interceptor;
}
}8.3 自动填充优化
java
@Component
public class DefaultDBFieldHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
this.strictInsertFill(metaObject, "creator", String.class, SecurityFrameworkUtils.getLoginUserId().toString());
}
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
this.strictUpdateFill(metaObject, "updater", String.class, SecurityFrameworkUtils.getLoginUserId().toString());
}
}9. 监控和调优
9.1 慢查询监控
sql
-- 开启慢查询日志
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;
SET GLOBAL log_queries_not_using_indexes = 'ON';
-- 查看慢查询日志
SHOW VARIABLES LIKE '%slow_query%';
-- 分析慢查询
SELECT * FROM mysql.slow_log ORDER BY query_time DESC LIMIT 10;9.2 性能监控
sql
-- 查看连接数
SHOW STATUS LIKE 'Threads_connected';
SHOW STATUS LIKE 'Max_used_connections';
-- 查看查询统计
SHOW STATUS LIKE 'Questions';
SHOW STATUS LIKE 'Queries';
-- 查看缓存命中率
SHOW STATUS LIKE 'Qcache%';
SHOW STATUS LIKE 'Key%';
-- 查看表锁
SHOW OPEN TABLES WHERE In_use > 0;
-- 查看锁等待
SHOW ENGINE INNODB STATUS;9.3 性能分析工具
sql
-- 使用 EXPLAIN 分析查询
EXPLAIN SELECT * FROM system_users WHERE username = 'admin';
-- 使用 EXPLAIN ANALYZE 分析查询(MySQL 8.0+)
EXPLAIN ANALYZE SELECT * FROM system_users WHERE username = 'admin';
-- 使用 SHOW PROFILE 分析查询
SET profiling = 1;
SELECT * FROM system_users WHERE username = 'admin';
SHOW PROFILE;
SHOW PROFILE FOR QUERY 1;10. 常见性能问题
10.1 慢查询
问题:查询执行时间过长
解决方案:
- 使用 EXPLAIN 分析查询计划
- 为查询条件添加索引
- 优化查询语句,避免全表扫描
- 使用覆盖索引
- 分页查询使用游标分页
10.2 连接池耗尽
问题:数据库连接池耗尽
解决方案:
- 增加连接池大小
- 减少连接持有时间
- 使用连接池监控
- 优化慢查询
- 使用读写分离
10.3 锁等待
问题:出现大量锁等待
解决方案:
- 优化事务范围
- 减少事务持有时间
- 使用乐观锁代替悲观锁
- 避免长事务
- 使用读写分离
10.4 缓存穿透
问题:大量请求查询不存在的数据
解决方案:
- 使用布隆过滤器
- 缓存空值
- 使用互斥锁
- 限流降级
10.5 缓存雪崩
问题:大量缓存同时失效
解决方案:
- 设置随机过期时间
- 使用多级缓存
- 缓存预热
- 使用熔断降级
注意:本文档持续更新中,如有问题请及时反馈。
