Skip to content

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.Driver
sql
-- 开启慢查询日志
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.log

3. 表结构优化

3.1 数据类型选择

数据类型存储空间使用场景优化建议
TINYINT1字节小范围整数状态、类型
SMALLINT2字节中等范围整数数量、排序
INT4字节大范围整数ID、数量
BIGINT8字节超大范围整数主键、外键
DECIMAL(M,D)M+2字节精确数值金额、价格
VARCHAR(N)N+1或N+2字节变长字符串用户名、邮箱
CHAR(N)N字节定长字符串固定编码
TEXT65535字节长文本内容、描述
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 的 Repository

4.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.dataobject

8.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 慢查询

问题:查询执行时间过长

解决方案

  1. 使用 EXPLAIN 分析查询计划
  2. 为查询条件添加索引
  3. 优化查询语句,避免全表扫描
  4. 使用覆盖索引
  5. 分页查询使用游标分页

10.2 连接池耗尽

问题:数据库连接池耗尽

解决方案

  1. 增加连接池大小
  2. 减少连接持有时间
  3. 使用连接池监控
  4. 优化慢查询
  5. 使用读写分离

10.3 锁等待

问题:出现大量锁等待

解决方案

  1. 优化事务范围
  2. 减少事务持有时间
  3. 使用乐观锁代替悲观锁
  4. 避免长事务
  5. 使用读写分离

10.4 缓存穿透

问题:大量请求查询不存在的数据

解决方案

  1. 使用布隆过滤器
  2. 缓存空值
  3. 使用互斥锁
  4. 限流降级

10.5 缓存雪崩

问题:大量缓存同时失效

解决方案

  1. 设置随机过期时间
  2. 使用多级缓存
  3. 缓存预热
  4. 使用熔断降级

注意:本文档持续更新中,如有问题请及时反馈。