← 返回首页
🏎️

数据库优化:索引与读写分离

📂 architecture ⏱ 4 min 683 words

数据库优化:索引与读写分离

索引优化策略

合理的索引设计是提升数据库查询性能的关键,需要根据查询模式和数据特征进行优化。

-- 复合索引设计
-- 遵循最左前缀原则
CREATE INDEX idx_user_orders ON orders(user_id, order_date, status);

-- 覆盖索引(避免回表)
CREATE INDEX idx_covering ON orders(user_id, order_date, total_amount, status);

-- 部分索引(只索引需要的数据)
CREATE INDEX idx_active_users ON users(email) WHERE status = 'active';

-- 表达式索引
CREATE INDEX idx_email_lower ON users(LOWER(email));
// 索引管理服务
@Component
public class IndexManager {
    
    private final JdbcTemplate jdbcTemplate;
    private final MetricsExporter metrics;
    
    // 分析索引使用情况
    public List<IndexUsage> analyzeIndexUsage(String schema) {
        String sql = """
            SELECT 
                schemaname,
                tablename,
                indexname,
                idx_scan as scans,
                idx_tup_read as tuples_read,
                idx_tup_fetch as tuples_fetched,
                pg_size_pretty(pg_relation_size(indexrelid)) as index_size
            FROM pg_stat_user_indexes
            WHERE schemaname = ?
            ORDER BY idx_scan DESC
            """;
        
        return jdbcTemplate.query(sql, new Object[]{schema},
            (rs, rowNum) -> IndexUsage.builder()
                .schema(rs.getString("schemaname"))
                .table(rs.getString("tablename"))
                .index(rs.getString("indexname"))
                .scans(rs.getLong("scans"))
                .tuplesRead(rs.getLong("tuples_read"))
                .tuplesFetched(rs.getLong("tuples_fetched"))
                .size(rs.getString("index_size"))
                .build());
    }
    
    // 识别未使用的索引
    public List<String> findUnusedIndexes(String schema) {
        String sql = """
            SELECT indexname
            FROM pg_stat_user_indexes
            WHERE schemaname = ?
              AND idx_scan = 0
              AND indexrelid NOT IN (
                  SELECT conindid FROM pg_constraint WHERE contype = 'p'
              )
            """;
        
        return jdbcTemplate.queryForList(sql, String.class, schema);
    }
    
    // 识别缺失索引
    public List<MissingIndex> findMissingIndexes(String schema) {
        String sql = """
            SELECT 
                relname as table_name,
                seq_scan,
                seq_tup_read,
                idx_scan,
                CASE WHEN seq_scan > 0 
                     THEN seq_tup_read / seq_scan 
                     ELSE 0 END as avg_rows_per_scan
            FROM pg_stat_user_tables
            WHERE schemaname = ?
              AND seq_scan > 100
              AND seq_tup_read > 10000
              AND idx_scan = 0
            ORDER BY seq_tup_read DESC
            """;
        
        return jdbcTemplate.query(sql, new Object[]{schema},
            (rs, rowNum) -> MissingIndex.builder()
                .table(rs.getString("table_name"))
                .seqScans(rs.getLong("seq_scan"))
                .tuplesScanned(rs.getLong("seq_tup_read"))
                .avgRowsPerScan(rs.getLong("avg_rows_per_scan"))
                .build());
    }
}

查询优化

// 查询优化器
@Component
public class QueryOptimizer {
    
    private final JdbcTemplate jdbcTemplate;
    private final QueryPlanAnalyzer planAnalyzer;
    
    // 分析查询计划
    public QueryPlan analyzeQuery(String sql) {
        // 获取执行计划
        String explainSql = "EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) " + sql;
        
        String planJson = jdbcTemplate.queryForObject(explainSql, String.class);
        
        return planAnalyzer.parse(planJson);
    }
    
    // 优化建议
    public List<OptimizationSuggestion> getSuggestions(String sql) {
        QueryPlan plan = analyzeQuery(sql);
        List<OptimizationSuggestion> suggestions = new ArrayList<>();
        
        // 检查全表扫描
        if (plan.hasSequentialScan()) {
            suggestions.add(OptimizationSuggestion.builder()
                .type("INDEX")
                .description("检测到全表扫描,建议添加索引")
                .impact("HIGH")
                .build());
        }
        
        // 检查排序操作
        if (plan.hasSort()) {
            suggestions.add(OptimizationSuggestion.builder()
                .type("INDEX")
                .description("检测到排序操作,建议添加排序索引")
                .impact("MEDIUM")
                .build());
        }
        
        // 检查嵌套循环
        if (plan.hasNestedLoop()) {
            suggestions.add(OptimizationSuggestion.builder()
                .type("QUERY_REWRITE")
                .description("检测到嵌套循环,建议重写查询")
                .impact("MEDIUM")
                .build());
        }
        
        return suggestions;
    }
}

// 批量查询优化
@Component
public class BatchQueryOptimizer {
    
    // IN子句优化
    public <T> List<T> batchInClause(String column, List<String> values, 
                                      Function<ResultSet, T> mapper) {
        int batchSize = 1000;
        List<T> results = new ArrayList<>();
        
        for (int i = 0; i < values.size(); i += batchSize) {
            List<String> batch = values.subList(i, 
                Math.min(i + batchSize, values.size()));
            
            String placeholders = batch.stream()
                .map(v -> "?")
                .collect(Collectors.joining(","));
            
            String sql = String.format(
                "SELECT * FROM table WHERE %s IN (%s)", 
                column, placeholders);
            
            results.addAll(jdbcTemplate.query(sql, 
                batch.toArray(), 
                (rs, rowNum) -> mapper.apply(rs)));
        }
        
        return results;
    }
    
    // 分页查询优化
    public <T> List<T> optimizedPaginate(String baseSql, int offset, 
                                          int limit, Object[] params) {
        // 使用游标分页代替OFFSET
        String cursorSql = baseSql + " ORDER BY id LIMIT ?";
        
        Object[] newParams = Arrays.copyOf(params, params.length + 1);
        newParams[params.length] = limit;
        
        return jdbcTemplate.query(cursorSql, newParams,
            (rs, rowNum) -> mapResult(rs));
    }
}

读写分离架构

// 读写分离数据源配置
@Configuration
public class ReadWriteSplittingConfig {
    
    @Bean
    @Primary
    public DataSource routingDataSource(
            @Qualifier("writeDataSource") DataSource writeDataSource,
            @Qualifier("readDataSource") DataSource readDataSource) {
        
        ReadWriteRoutingDataSource routingDataSource = 
            new ReadWriteRoutingDataSource();
        
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("write", writeDataSource);
        targetDataSources.put("read", readDataSource);
        
        routingDataSource.setTargetDataSources(targetDataSources);
        routingDataSource.setDefaultTargetDataSource(writeDataSource);
        
        return routingDataSource;
    }
    
    @Bean("writeDataSource")
    @ConfigurationProperties("spring.datasource.write")
    public DataSource writeDataSource() {
        return DataSourceBuilder.create().build();
    }
    
    @Bean("readDataSource")
    @ConfigurationProperties("spring.datasource.read")
    public DataSource readDataSource() {
        return DataSourceBuilder.create().build();
    }
}

// 数据源路由
public class ReadWriteRoutingDataSource extends AbstractRoutingDataSource {
    
    private static final ThreadLocal<String> CONTEXT = new ThreadLocal<>();
    
    @Override
    protected Object determineCurrentLookupKey() {
        return CONTEXT.get();
    }
    
    public static void setRead() {
        CONTEXT.set("read");
    }
    
    public static void setWrite() {
        CONTEXT.set("write");
    }
    
    public static void clear() {
        CONTEXT.remove();
    }
}

// 读写分离拦截器
@Aspect
@Component
public class ReadWriteSplittingAspect {
    
    @Around("@annotation(ReadOnly)")
    public Object handleReadOnly(ProceedingJoinPoint joinPoint) throws Throwable {
        try {
            ReadWriteRoutingDataSource.setRead();
            return joinPoint.proceed();
        } finally {
            ReadWriteRoutingDataSource.clear();
        }
    }
    
    @Around("@annotation(WriteOnly)")
    public Object handleWriteOnly(ProceedingJoinPoint joinPoint) throws Throwable {
        try {
            ReadWriteRoutingDataSource.setWrite();
            return joinPoint.proceed();
        } finally {
            ReadWriteRoutingDataSource.clear();
        }
    }
}

// 使用示例
@Service
public class OrderService {
    
    @ReadOnly
    public List<Order> getOrders(Long userId) {
        return orderRepository.findByUserId(userId);
    }
    
    @WriteOnly
    public Order createOrder(OrderRequest request) {
        return orderRepository.save(createOrderFromRequest(request));
    }
}

数据库性能监控

# 数据库性能配置
database:
  optimization:
    # 连接池配置
    connection_pool:
      type: "hikari"
      maximum_pool_size: 100
      minimum_idle: 10
      idle_timeout: "30m"
      max_lifetime: "1h"
      connection_timeout: "30s"
    
    # 查询超时
    query_timeout: "30s"
    
    # 慢查询日志
    slow_query_log:
      enabled: true
      threshold: "1s"
      log_path: "/var/log/mysql/slow-query.log"
    
    # 读写分离
    read_write_splitting:
      enabled: true
      read_strategy: "round_robin"
      health_check_interval: "10s"
    
    # 监控
    monitoring:
      enabled: true
      metrics:
        - "connection_pool_active"
        - "connection_pool_idle"
        - "query_duration"
        - "slow_queries"
        - "deadlocks"
      
      alerts:
        - metric: "connection_pool_active"
          threshold: 80
          action: "warning"
        
        - metric: "query_duration_avg"
          threshold: 1000
          action: "warning"
        
        - metric: "deadlocks"
          threshold: 1
          action: "critical"

数据库优化通过索引优化、查询优化、读写分离和性能监控,显著提升数据库的查询性能和系统整体吞吐量。