1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336
| package com.mysql.optimization;
import java.sql.*; import java.util.*; import java.util.regex.Pattern;
public class IndexOptimizer { private final Connection connection; private final String databaseName; public IndexOptimizer(Connection connection, String databaseName) { this.connection = connection; this.databaseName = databaseName; } public static class IndexAnalysis { private String tableName; private String indexName; private String columnName; private long cardinality; private String indexType; private boolean isUnique; private double selectivity; private long indexSize; private String recommendation; public IndexAnalysis(String tableName, String indexName, String columnName) { this.tableName = tableName; this.indexName = indexName; this.columnName = columnName; } public void setCardinality(long cardinality) { this.cardinality = cardinality; } public void setIndexType(String indexType) { this.indexType = indexType; } public void setUnique(boolean unique) { isUnique = unique; } public void setSelectivity(double selectivity) { this.selectivity = selectivity; } public void setIndexSize(long indexSize) { this.indexSize = indexSize; } public void setRecommendation(String recommendation) { this.recommendation = recommendation; } @Override public String toString() { return String.format( "Table: %s, Index: %s, Column: %s\n" + "Cardinality: %d, Type: %s, Unique: %s\n" + "Selectivity: %.4f, Size: %d bytes\n" + "Recommendation: %s\n", tableName, indexName, columnName, cardinality, indexType, isUnique, selectivity, indexSize, recommendation ); } } public List<IndexAnalysis> analyzeAllIndexes() throws SQLException { List<IndexAnalysis> analyses = new ArrayList<>(); List<String> tables = getAllTables(); for (String table : tables) { analyses.addAll(analyzeTableIndexes(table)); } return analyses; } private List<String> getAllTables() throws SQLException { List<String> tables = new ArrayList<>(); String sql = """ SELECT table_name FROM information_schema.tables WHERE table_schema = ? AND table_type = 'BASE TABLE' """; try (PreparedStatement pstmt = connection.prepareStatement(sql)) { pstmt.setString(1, databaseName); try (ResultSet rs = pstmt.executeQuery()) { while (rs.next()) { tables.add(rs.getString("table_name")); } } } return tables; } private List<IndexAnalysis> analyzeTableIndexes(String tableName) throws SQLException { List<IndexAnalysis> analyses = new ArrayList<>(); String indexSQL = """ SELECT s.index_name, s.column_name, s.cardinality, s.non_unique, i.index_type FROM information_schema.statistics s JOIN information_schema.statistics i ON s.index_name = i.index_name AND s.table_name = i.table_name WHERE s.table_schema = ? AND s.table_name = ? ORDER BY s.index_name, s.seq_in_index """; try (PreparedStatement pstmt = connection.prepareStatement(indexSQL)) { pstmt.setString(1, databaseName); pstmt.setString(2, tableName); try (ResultSet rs = pstmt.executeQuery()) { while (rs.next()) { IndexAnalysis analysis = new IndexAnalysis( tableName, rs.getString("index_name"), rs.getString("column_name") ); analysis.setCardinality(rs.getLong("cardinality")); analysis.setIndexType(rs.getString("index_type")); analysis.setUnique(rs.getInt("non_unique") == 0); double selectivity = calculateSelectivity(tableName, rs.getString("column_name")); analysis.setSelectivity(selectivity); long indexSize = getIndexSize(tableName, rs.getString("index_name")); analysis.setIndexSize(indexSize); String recommendation = generateRecommendation(analysis); analysis.setRecommendation(recommendation); analyses.add(analysis); } } } return analyses; } private double calculateSelectivity(String tableName, String columnName) throws SQLException { String sql = String.format(""" SELECT COUNT(DISTINCT %s) / COUNT(*) as selectivity FROM %s """, columnName, tableName); try (Statement stmt = connection.createStatement(); ResultSet rs = stmt.executeQuery(sql)) { if (rs.next()) { return rs.getDouble("selectivity"); } } catch (SQLException e) { return 0.0; } return 0.0; } private long getIndexSize(String tableName, String indexName) throws SQLException { String sql = """ SELECT ROUND(((index_length) / 1024 / 1024), 2) AS index_size_mb FROM information_schema.tables WHERE table_schema = ? AND table_name = ? """; try (PreparedStatement pstmt = connection.prepareStatement(sql)) { pstmt.setString(1, databaseName); pstmt.setString(2, tableName); try (ResultSet rs = pstmt.executeQuery()) { if (rs.next()) { return (long) (rs.getDouble("index_size_mb") * 1024 * 1024); } } } return 0; } private String generateRecommendation(IndexAnalysis analysis) { StringBuilder recommendation = new StringBuilder(); if (analysis.selectivity < 0.01) { recommendation.append("选择性过低(").append(String.format("%.4f", analysis.selectivity)) .append("),考虑删除此索引; "); } else if (analysis.selectivity > 0.95) { recommendation.append("选择性很高,适合作为主键或唯一索引; "); } if (analysis.cardinality < 100) { recommendation.append("基数较低,可能不需要索引; "); } if (analysis.indexSize > 100 * 1024 * 1024) { recommendation.append("索引过大,考虑优化或分区; "); } if ("BTREE".equals(analysis.indexType)) { recommendation.append("B-Tree索引适合范围查询; "); } if (recommendation.length() == 0) { recommendation.append("索引配置合理"); } return recommendation.toString(); } public List<String> findRedundantIndexes() throws SQLException { List<String> redundantIndexes = new ArrayList<>(); String sql = """ SELECT table_name, GROUP_CONCAT(DISTINCT index_name) as indexes, GROUP_CONCAT(column_name ORDER BY seq_in_index) as columns FROM information_schema.statistics WHERE table_schema = ? AND index_name != 'PRIMARY' GROUP BY table_name, index_name HAVING COUNT(*) > 1 """; try (PreparedStatement pstmt = connection.prepareStatement(sql)) { pstmt.setString(1, databaseName); try (ResultSet rs = pstmt.executeQuery()) { while (rs.next()) { String tableName = rs.getString("table_name"); String indexes = rs.getString("indexes"); String columns = rs.getString("columns"); redundantIndexes.add(String.format( "Table: %s, Indexes: %s, Columns: %s", tableName, indexes, columns )); } } } return redundantIndexes; } public List<String> findUnusedIndexes() throws SQLException { List<String> unusedIndexes = new ArrayList<>(); String sql = """ SELECT object_schema, object_name, index_name FROM performance_schema.table_io_waits_summary_by_index_usage WHERE index_name IS NOT NULL AND index_name != 'PRIMARY' AND count_star = 0 AND object_schema = ? """; try (PreparedStatement pstmt = connection.prepareStatement(sql)) { pstmt.setString(1, databaseName); try (ResultSet rs = pstmt.executeQuery()) { while (rs.next()) { unusedIndexes.add(String.format( "Schema: %s, Table: %s, Index: %s", rs.getString("object_schema"), rs.getString("object_name"), rs.getString("index_name") )); } } } catch (SQLException e) { unusedIndexes.add("无法检测未使用索引:performance_schema未启用或无权限"); } return unusedIndexes; } public void generateIndexOptimizationReport() throws SQLException { System.out.println("=== MySQL索引优化报告 ===\n"); List<IndexAnalysis> analyses = analyzeAllIndexes(); System.out.println("1. 索引分析结果:"); for (IndexAnalysis analysis : analyses) { System.out.println(analysis); } List<String> redundantIndexes = findRedundantIndexes(); System.out.println("2. 冗余索引:"); if (redundantIndexes.isEmpty()) { System.out.println("未发现冗余索引"); } else { redundantIndexes.forEach(System.out::println); } List<String> unusedIndexes = findUnusedIndexes(); System.out.println("\n3. 未使用索引:"); if (unusedIndexes.isEmpty()) { System.out.println("未发现未使用索引"); } else { unusedIndexes.forEach(System.out::println); } System.out.println("\n=== 优化建议 ==="); System.out.println("1. 删除选择性低于1%的索引"); System.out.println("2. 合并冗余的复合索引"); System.out.println("3. 为高频查询字段添加索引"); System.out.println("4. 定期监控索引使用情况"); } }
|