1. MySQL千万级表变更字段概述

MySQL千万级表变更字段是数据库运维中的重要技术,传统的ALTER TABLE操作会锁表,影响业务正常运行。通过在线DDL、pt-online-schema-change、gh-ost等工具可以实现不锁表的表结构变更,保证业务的高可用性。系统具备在线DDL、表结构变更、数据迁移、性能优化、监控告警等功能。本文将详细介绍MySQL千万级表变更字段的原理、实现方法、性能优化技巧以及在运维实战中的应用。

1.1 MySQL千万级表变更字段核心价值

  1. 业务连续性: 保证业务在表结构变更期间的连续性
  2. 零停机时间: 实现零停机时间的表结构变更
  3. 数据一致性: 确保表结构变更过程中的数据一致性
  4. 性能优化: 优化表结构变更的性能和效率
  5. 风险控制: 降低表结构变更的风险

1.2 MySQL千万级表变更字段场景

  • 字段添加: 添加新字段到现有表
  • 字段删除: 删除不需要的字段
  • 字段修改: 修改字段类型、长度等属性
  • 索引变更: 添加、删除、修改索引
  • 表结构优化: 优化表结构设计

1.3 MySQL表变更技术特性

  • 在线DDL: MySQL 5.6+支持的在线DDL操作
  • pt-online-schema-change: Percona Toolkit的在线表变更工具
  • gh-ost: GitHub开源的在线表变更工具
  • 数据迁移: 支持数据迁移和同步
  • 回滚机制: 支持变更回滚

2. MySQL千万级表变更字段基础实现

2.1 MySQL表变更配置类

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
/**
* MySQL表变更配置类
* @author 运维实战
*/
@Configuration
@EnableConfigurationProperties(MySQLTableAlterProperties.class)
public class MySQLTableAlterConfig {

@Autowired
private MySQLTableAlterProperties properties;

/**
* MySQL表变更服务
* @return MySQL表变更服务
*/
@Bean
public MySQLTableAlterService mysqlTableAlterService() {
return new MySQLTableAlterService();
}

/**
* MySQL在线DDL服务
* @return MySQL在线DDL服务
*/
@Bean
public MySQLOnlineDDLService mysqlOnlineDDLService() {
return new MySQLOnlineDDLService();
}

/**
* MySQL表结构变更服务
* @return MySQL表结构变更服务
*/
@Bean
public MySQLTableStructureService mysqlTableStructureService() {
return new MySQLTableStructureService();
}

/**
* MySQL数据迁移服务
* @return MySQL数据迁移服务
*/
@Bean
public MySQLDataMigrationService mysqlDataMigrationService() {
return new MySQLDataMigrationService();
}

/**
* MySQL表变更监控服务
* @return MySQL表变更监控服务
*/
@Bean
public MySQLTableAlterMonitorService mysqlTableAlterMonitorService() {
return new MySQLTableAlterMonitorService();
}

private static final Logger logger = LoggerFactory.getLogger(MySQLTableAlterConfig.class);
}

2.2 MySQL表变更属性配置

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
/**
* MySQL表变更属性配置
* @author 运维实战
*/
@Data
@ConfigurationProperties(prefix = "mysql.table-alter")
public class MySQLTableAlterProperties {

/**
* 是否启用MySQL表变更
*/
private boolean enableTableAlter = true;

/**
* MySQL连接配置
*/
private DatabaseConfig database = new DatabaseConfig();

/**
* 表变更策略
*/
private String alterStrategy = "ONLINE_DDL"; // ONLINE_DDL, PT_ONLINE_SCHEMA_CHANGE, GH_OST

/**
* 是否启用在线DDL
*/
private boolean enableOnlineDDL = true;

/**
* 在线DDL超时时间(秒)
*/
private long onlineDDLTimeout = 3600;

/**
* 是否启用pt-online-schema-change
*/
private boolean enablePtOnlineSchemaChange = false;

/**
* pt-online-schema-change路径
*/
private String ptOnlineSchemaChangePath = "/usr/bin/pt-online-schema-change";

/**
* pt-online-schema-change超时时间(秒)
*/
private long ptOnlineSchemaChangeTimeout = 7200;

/**
* 是否启用gh-ost
*/
private boolean enableGhOst = false;

/**
* gh-ost路径
*/
private String ghOstPath = "/usr/bin/gh-ost";

/**
* gh-ost超时时间(秒)
*/
private long ghOstTimeout = 7200;

/**
* 是否启用数据迁移
*/
private boolean enableDataMigration = true;

/**
* 数据迁移批次大小
*/
private int dataMigrationBatchSize = 1000;

/**
* 数据迁移间隔(毫秒)
*/
private long dataMigrationInterval = 100;

/**
* 是否启用变更回滚
*/
private boolean enableRollback = true;

/**
* 回滚超时时间(秒)
*/
private long rollbackTimeout = 1800;

/**
* 是否启用性能监控
*/
private boolean enablePerformanceMonitoring = true;

/**
* 性能监控间隔(毫秒)
*/
private long performanceMonitoringInterval = 30000;

/**
* 是否启用监控
*/
private boolean enableMonitoring = true;

/**
* 监控间隔(毫秒)
*/
private long monitorInterval = 30000;

/**
* 是否启用告警
*/
private boolean enableAlert = true;

/**
* 表变更超时告警阈值(秒)
*/
private long tableAlterTimeoutAlertThreshold = 3600;

/**
* 数据迁移失败告警阈值
*/
private int dataMigrationFailureAlertThreshold = 5;

/**
* 表变更失败告警阈值
*/
private int tableAlterFailureAlertThreshold = 3;

/**
* 数据库配置类
*/
@Data
public static class DatabaseConfig {
/**
* 数据库URL
*/
private String url = "jdbc:mysql://localhost:3306/test";

/**
* 数据库用户名
*/
private String username = "root";

/**
* 数据库密码
*/
private String password = "password";

/**
* 数据库驱动
*/
private String driverClassName = "com.mysql.cj.jdbc.Driver";

/**
* 连接池配置
*/
private ConnectionPoolConfig connectionPool = new ConnectionPoolConfig();
}

/**
* 连接池配置类
*/
@Data
public static class ConnectionPoolConfig {
/**
* 最小连接数
*/
private int minIdle = 5;

/**
* 最大连接数
*/
private int maxActive = 20;

/**
* 连接超时时间(毫秒)
*/
private long connectionTimeout = 30000;

/**
* 空闲连接超时时间(毫秒)
*/
private long idleTimeout = 600000;

/**
* 最大生命周期(毫秒)
*/
private long maxLifetime = 1800000;
}
}

2.3 表变更数据模型类

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
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
/**
* 表变更数据模型类
* @author 运维实战
*/
@Data
public class TableAlterRequest {

/**
* 变更ID
*/
private String alterId;

/**
* 数据库名称
*/
private String databaseName;

/**
* 表名称
*/
private String tableName;

/**
* 变更类型
*/
private String alterType; // ADD_COLUMN, DROP_COLUMN, MODIFY_COLUMN, ADD_INDEX, DROP_INDEX

/**
* 变更SQL
*/
private String alterSql;

/**
* 变更描述
*/
private String description;

/**
* 变更策略
*/
private String strategy; // ONLINE_DDL, PT_ONLINE_SCHEMA_CHANGE, GH_OST

/**
* 变更状态
*/
private String status; // PENDING, RUNNING, COMPLETED, FAILED, ROLLBACK

/**
* 开始时间
*/
private Long startTime;

/**
* 结束时间
*/
private Long endTime;

/**
* 错误信息
*/
private String errorMessage;

/**
* 变更参数
*/
private Map<String, Object> parameters;

/**
* 扩展属性
*/
private Map<String, Object> extendedProperties;

public TableAlterRequest() {
this.alterId = UUID.randomUUID().toString();
this.status = "PENDING";
this.parameters = new HashMap<>();
this.extendedProperties = new HashMap<>();
}

public TableAlterRequest(String databaseName, String tableName, String alterType, String alterSql) {
this();
this.databaseName = databaseName;
this.tableName = tableName;
this.alterType = alterType;
this.alterSql = alterSql;
}

/**
* 验证表变更请求
* @return 是否有效
*/
public boolean validate() {
if (alterId == null || alterId.isEmpty()) {
return false;
}

if (databaseName == null || databaseName.isEmpty()) {
return false;
}

if (tableName == null || tableName.isEmpty()) {
return false;
}

if (alterType == null || alterType.isEmpty()) {
return false;
}

if (alterSql == null || alterSql.isEmpty()) {
return false;
}

return true;
}

/**
* 计算变更耗时
* @return 变更耗时(毫秒)
*/
public long getDuration() {
if (startTime != null && endTime != null) {
return endTime - startTime;
}
return 0;
}

/**
* 是否完成
* @return 是否完成
*/
public boolean isCompleted() {
return "COMPLETED".equals(status);
}

/**
* 是否失败
* @return 是否失败
*/
public boolean isFailed() {
return "FAILED".equals(status);
}

/**
* 添加变更参数
* @param key 键
* @param value 值
*/
public void addParameter(String key, Object value) {
if (parameters == null) {
parameters = new HashMap<>();
}
parameters.put(key, value);
}

/**
* 添加扩展属性
* @param key 键
* @param value 值
*/
public void addExtendedProperty(String key, Object value) {
if (extendedProperties == null) {
extendedProperties = new HashMap<>();
}
extendedProperties.put(key, value);
}

/**
* 获取扩展属性
* @param key 键
* @return
*/
public Object getExtendedProperty(String key) {
return extendedProperties != null ? extendedProperties.get(key) : null;
}
}

/**
* 表变更结果类
* @author 运维实战
*/
@Data
public class TableAlterResult {

private boolean success;
private String alterId;
private String databaseName;
private String tableName;
private String alterType;
private String status;
private String error;
private long startTime;
private long endTime;

public TableAlterResult() {
this.success = false;
}

public TableAlterResult(String alterId, String databaseName, String tableName, String alterType) {
this();
this.alterId = alterId;
this.databaseName = databaseName;
this.tableName = tableName;
this.alterType = alterType;
}

/**
* 获取变更耗时
* @return 变更耗时(毫秒)
*/
public long getDuration() {
return endTime - startTime;
}

/**
* 是否成功
* @return 是否成功
*/
public boolean isSuccess() {
return success;
}
}

/**
* 表结构信息类
* @author 运维实战
*/
@Data
public class TableStructureInfo {

/**
* 数据库名称
*/
private String databaseName;

/**
* 表名称
*/
private String tableName;

/**
* 表引擎
*/
private String engine;

/**
* 表字符集
*/
private String charset;

/**
* 表排序规则
*/
private String collation;

/**
* 表行数
*/
private Long rowCount;

/**
* 表大小(字节)
*/
private Long tableSize;

/**
* 索引大小(字节)
*/
private Long indexSize;

/**
* 字段信息
*/
private List<ColumnInfo> columns;

/**
* 索引信息
*/
private List<IndexInfo> indexes;

/**
* 创建时间
*/
private Long createTime;

/**
* 更新时间
*/
private Long updateTime;

public TableStructureInfo() {
this.columns = new ArrayList<>();
this.indexes = new ArrayList<>();
}

public TableStructureInfo(String databaseName, String tableName) {
this();
this.databaseName = databaseName;
this.tableName = tableName;
}

/**
* 验证表结构信息
* @return 是否有效
*/
public boolean validate() {
if (databaseName == null || databaseName.isEmpty()) {
return false;
}

if (tableName == null || tableName.isEmpty()) {
return false;
}

return true;
}

/**
* 添加字段信息
* @param columnInfo 字段信息
*/
public void addColumn(ColumnInfo columnInfo) {
if (columns == null) {
columns = new ArrayList<>();
}
columns.add(columnInfo);
}

/**
* 添加索引信息
* @param indexInfo 索引信息
*/
public void addIndex(IndexInfo indexInfo) {
if (indexes == null) {
indexes = new ArrayList<>();
}
indexes.add(indexInfo);
}
}

/**
* 字段信息类
* @author 运维实战
*/
@Data
public class ColumnInfo {

/**
* 字段名称
*/
private String columnName;

/**
* 字段类型
*/
private String columnType;

/**
* 是否允许NULL
*/
private Boolean nullable;

/**
* 默认值
*/
private String defaultValue;

/**
* 字段注释
*/
private String comment;

/**
* 字段位置
*/
private Integer position;

public ColumnInfo() {}

public ColumnInfo(String columnName, String columnType) {
this.columnName = columnName;
this.columnType = columnType;
}
}

/**
* 索引信息类
* @author 运维实战
*/
@Data
public class IndexInfo {

/**
* 索引名称
*/
private String indexName;

/**
* 索引类型
*/
private String indexType;

/**
* 索引字段
*/
private List<String> columns;

/**
* 是否唯一索引
*/
private Boolean unique;

/**
* 索引注释
*/
private String comment;

public IndexInfo() {
this.columns = new ArrayList<>();
}

public IndexInfo(String indexName, String indexType) {
this();
this.indexName = indexName;
this.indexType = indexType;
}
}

2.4 基础MySQL表变更服务

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
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
/**
* 基础MySQL表变更服务
* @author 运维实战
*/
@Service
public class MySQLTableAlterService {

@Autowired
private MySQLTableAlterProperties properties;

@Autowired
private DataSource dataSource;

@Autowired
private MySQLOnlineDDLService mysqlOnlineDDLService;

@Autowired
private MySQLTableStructureService mysqlTableStructureService;

@Autowired
private MySQLDataMigrationService mysqlDataMigrationService;

@Autowired
private MySQLTableAlterMonitorService mysqlTableAlterMonitorService;

private static final Logger logger = LoggerFactory.getLogger(MySQLTableAlterService.class);

/**
* 执行表变更
* @param alterRequest 表变更请求
* @return 表变更结果
*/
public TableAlterResult executeTableAlter(TableAlterRequest alterRequest) {
logger.info("执行表变更,变更ID: {}, 表: {}.{}",
alterRequest.getAlterId(), alterRequest.getDatabaseName(), alterRequest.getTableName());

TableAlterResult result = new TableAlterResult(
alterRequest.getAlterId(), alterRequest.getDatabaseName(),
alterRequest.getTableName(), alterRequest.getAlterType());
result.setStartTime(System.currentTimeMillis());

try {
// 验证表变更请求
if (!alterRequest.validate()) {
result.setSuccess(false);
result.setError("表变更请求验证失败");
result.setEndTime(System.currentTimeMillis());
return result;
}

// 更新状态为运行中
alterRequest.setStatus("RUNNING");
alterRequest.setStartTime(System.currentTimeMillis());

// 根据变更策略执行表变更
TableAlterResult alterResult = null;
switch (properties.getAlterStrategy().toUpperCase()) {
case "ONLINE_DDL":
alterResult = mysqlOnlineDDLService.executeOnlineDDL(alterRequest);
break;
case "PT_ONLINE_SCHEMA_CHANGE":
alterResult = executePtOnlineSchemaChange(alterRequest);
break;
case "GH_OST":
alterResult = executeGhOst(alterRequest);
break;
default:
alterResult = mysqlOnlineDDLService.executeOnlineDDL(alterRequest);
break;
}

if (alterResult != null && alterResult.isSuccess()) {
result.setSuccess(true);
result.setStatus("COMPLETED");
result.setEndTime(System.currentTimeMillis());

// 更新变更请求状态
alterRequest.setStatus("COMPLETED");
alterRequest.setEndTime(System.currentTimeMillis());

// 记录变更成功指标
mysqlTableAlterMonitorService.recordTableAlter(alterRequest.getTableName(), true);

logger.info("表变更成功,变更ID: {}, 表: {}.{}, 耗时: {}ms",
alterRequest.getAlterId(), alterRequest.getDatabaseName(),
alterRequest.getTableName(), result.getDuration());
} else {
result.setSuccess(false);
result.setStatus("FAILED");
result.setError(alterResult != null ? alterResult.getError() : "表变更失败");
result.setEndTime(System.currentTimeMillis());

// 更新变更请求状态
alterRequest.setStatus("FAILED");
alterRequest.setEndTime(System.currentTimeMillis());
alterRequest.setErrorMessage(result.getError());

// 记录变更失败指标
mysqlTableAlterMonitorService.recordTableAlter(alterRequest.getTableName(), false);
}

return result;

} catch (Exception e) {
logger.error("表变更异常,变更ID: {}", alterRequest.getAlterId(), e);
result.setSuccess(false);
result.setStatus("FAILED");
result.setError("表变更异常: " + e.getMessage());
result.setEndTime(System.currentTimeMillis());

// 更新变更请求状态
alterRequest.setStatus("FAILED");
alterRequest.setEndTime(System.currentTimeMillis());
alterRequest.setErrorMessage(result.getError());

// 记录变更异常指标
mysqlTableAlterMonitorService.recordTableAlter(alterRequest.getTableName(), false);

return result;
}
}

/**
* 批量执行表变更
* @param alterRequests 表变更请求列表
* @return 批量表变更结果
*/
public TableAlterBatchResult batchExecuteTableAlter(List<TableAlterRequest> alterRequests) {
logger.info("批量执行表变更,数量: {}", alterRequests.size());

TableAlterBatchResult result = new TableAlterBatchResult();
result.setTotalCount(alterRequests.size());
result.setStartTime(System.currentTimeMillis());

try {
// 验证和预处理变更请求
List<TableAlterRequest> validRequests = new ArrayList<>();
List<TableAlterRequest> invalidRequests = new ArrayList<>();

for (TableAlterRequest alterRequest : alterRequests) {
if (alterRequest.validate()) {
validRequests.add(alterRequest);
} else {
invalidRequests.add(alterRequest);
logger.warn("表变更请求验证失败,变更ID: {}", alterRequest.getAlterId());
}
}

// 批量执行有效变更请求
if (!validRequests.isEmpty()) {
TableAlterBatchResult batchResult = executeBatchTableAlter(validRequests);

result.setSuccessCount(batchResult.getSuccessCount());
result.setFailureCount(batchResult.getFailureCount() + invalidRequests.size());
result.setResults(batchResult.getResults());
} else {
result.setFailureCount(alterRequests.size());
}

result.setEndTime(System.currentTimeMillis());

// 记录批量变更指标
mysqlTableAlterMonitorService.recordTableAlterBatch(alterRequests.size(), result.getSuccessCount());

logger.info("批量表变更完成,总数: {}, 成功: {}, 失败: {}, 耗时: {}ms",
result.getTotalCount(), result.getSuccessCount(), result.getFailureCount(), result.getDuration());

return result;

} catch (Exception e) {
logger.error("批量表变更异常", e);
result.setSuccess(false);
result.setError("批量表变更异常: " + e.getMessage());
result.setEndTime(System.currentTimeMillis());
return result;
}
}

/**
* 获取表结构信息
* @param databaseName 数据库名称
* @param tableName 表名称
* @return 表结构信息
*/
public TableStructureInfo getTableStructure(String databaseName, String tableName) {
logger.info("获取表结构信息,表: {}.{}", databaseName, tableName);

try {
TableStructureInfo tableStructure = mysqlTableStructureService.getTableStructure(databaseName, tableName);

if (tableStructure != null) {
logger.info("获取表结构信息成功,表: {}.{}, 字段数: {}, 索引数: {}",
databaseName, tableName, tableStructure.getColumns().size(), tableStructure.getIndexes().size());
} else {
logger.warn("表结构信息不存在,表: {}.{}", databaseName, tableName);
}

return tableStructure;

} catch (Exception e) {
logger.error("获取表结构信息异常,表: {}.{}", databaseName, tableName, e);
return null;
}
}

/**
* 回滚表变更
* @param alterRequest 表变更请求
* @return 回滚结果
*/
public TableAlterResult rollbackTableAlter(TableAlterRequest alterRequest) {
logger.info("回滚表变更,变更ID: {}, 表: {}.{}",
alterRequest.getAlterId(), alterRequest.getDatabaseName(), alterRequest.getTableName());

TableAlterResult result = new TableAlterResult(
alterRequest.getAlterId(), alterRequest.getDatabaseName(),
alterRequest.getTableName(), alterRequest.getAlterType());
result.setStartTime(System.currentTimeMillis());

try {
// 检查是否支持回滚
if (!properties.isEnableRollback()) {
result.setSuccess(false);
result.setError("回滚功能未启用");
result.setEndTime(System.currentTimeMillis());
return result;
}

// 执行回滚操作
boolean rollbackSuccess = executeRollback(alterRequest);

if (rollbackSuccess) {
result.setSuccess(true);
result.setStatus("ROLLBACK_COMPLETED");
result.setEndTime(System.currentTimeMillis());

// 更新变更请求状态
alterRequest.setStatus("ROLLBACK");

logger.info("表变更回滚成功,变更ID: {}, 表: {}.{}, 耗时: {}ms",
alterRequest.getAlterId(), alterRequest.getDatabaseName(),
alterRequest.getTableName(), result.getDuration());
} else {
result.setSuccess(false);
result.setStatus("ROLLBACK_FAILED");
result.setError("回滚操作失败");
result.setEndTime(System.currentTimeMillis());
}

return result;

} catch (Exception e) {
logger.error("表变更回滚异常,变更ID: {}", alterRequest.getAlterId(), e);
result.setSuccess(false);
result.setStatus("ROLLBACK_FAILED");
result.setError("表变更回滚异常: " + e.getMessage());
result.setEndTime(System.currentTimeMillis());
return result;
}
}

/**
* 执行pt-online-schema-change
* @param alterRequest 表变更请求
* @return 表变更结果
*/
private TableAlterResult executePtOnlineSchemaChange(TableAlterRequest alterRequest) {
logger.info("执行pt-online-schema-change,变更ID: {}", alterRequest.getAlterId());

TableAlterResult result = new TableAlterResult(
alterRequest.getAlterId(), alterRequest.getDatabaseName(),
alterRequest.getTableName(), alterRequest.getAlterType());
result.setStartTime(System.currentTimeMillis());

try {
// 构建pt-online-schema-change命令
String command = buildPtOnlineSchemaChangeCommand(alterRequest);

// 执行命令
Process process = Runtime.getRuntime().exec(command);

// 等待执行完成
boolean finished = process.waitFor(properties.getPtOnlineSchemaChangeTimeout(), TimeUnit.SECONDS);

if (finished && process.exitValue() == 0) {
result.setSuccess(true);
result.setStatus("COMPLETED");
result.setEndTime(System.currentTimeMillis());

logger.info("pt-online-schema-change执行成功,变更ID: {}, 耗时: {}ms",
alterRequest.getAlterId(), result.getDuration());
} else {
result.setSuccess(false);
result.setStatus("FAILED");
result.setError("pt-online-schema-change执行失败");
result.setEndTime(System.currentTimeMillis());
}

return result;

} catch (Exception e) {
logger.error("pt-online-schema-change执行异常,变更ID: {}", alterRequest.getAlterId(), e);
result.setSuccess(false);
result.setStatus("FAILED");
result.setError("pt-online-schema-change执行异常: " + e.getMessage());
result.setEndTime(System.currentTimeMillis());
return result;
}
}

/**
* 执行gh-ost
* @param alterRequest 表变更请求
* @return 表变更结果
*/
private TableAlterResult executeGhOst(TableAlterRequest alterRequest) {
logger.info("执行gh-ost,变更ID: {}", alterRequest.getAlterId());

TableAlterResult result = new TableAlterResult(
alterRequest.getAlterId(), alterRequest.getDatabaseName(),
alterRequest.getTableName(), alterRequest.getAlterType());
result.setStartTime(System.currentTimeMillis());

try {
// 构建gh-ost命令
String command = buildGhOstCommand(alterRequest);

// 执行命令
Process process = Runtime.getRuntime().exec(command);

// 等待执行完成
boolean finished = process.waitFor(properties.getGhOstTimeout(), TimeUnit.SECONDS);

if (finished && process.exitValue() == 0) {
result.setSuccess(true);
result.setStatus("COMPLETED");
result.setEndTime(System.currentTimeMillis());

logger.info("gh-ost执行成功,变更ID: {}, 耗时: {}ms",
alterRequest.getAlterId(), result.getDuration());
} else {
result.setSuccess(false);
result.setStatus("FAILED");
result.setError("gh-ost执行失败");
result.setEndTime(System.currentTimeMillis());
}

return result;

} catch (Exception e) {
logger.error("gh-ost执行异常,变更ID: {}", alterRequest.getAlterId(), e);
result.setSuccess(false);
result.setStatus("FAILED");
result.setError("gh-ost执行异常: " + e.getMessage());
result.setEndTime(System.currentTimeMillis());
return result;
}
}

/**
* 执行批量表变更
* @param alterRequests 表变更请求列表
* @return 批量表变更结果
*/
private TableAlterBatchResult executeBatchTableAlter(List<TableAlterRequest> alterRequests) {
TableAlterBatchResult result = new TableAlterBatchResult();
result.setTotalCount(alterRequests.size());
result.setStartTime(System.currentTimeMillis());

try {
List<TableAlterResult> results = new ArrayList<>();
int successCount = 0;
int failureCount = 0;

for (TableAlterRequest alterRequest : alterRequests) {
try {
TableAlterResult alterResult = executeTableAlter(alterRequest);
results.add(alterResult);

if (alterResult.isSuccess()) {
successCount++;
} else {
failureCount++;
}
} catch (Exception e) {
logger.error("批量表变更单条执行异常,变更ID: {}", alterRequest.getAlterId(), e);
failureCount++;
}
}

result.setSuccessCount(successCount);
result.setFailureCount(failureCount);
result.setResults(results);
result.setEndTime(System.currentTimeMillis());

return result;

} catch (Exception e) {
logger.error("执行批量表变更异常", e);
result.setSuccess(false);
result.setError("执行批量表变更异常: " + e.getMessage());
result.setEndTime(System.currentTimeMillis());
return result;
}
}

/**
* 执行回滚操作
* @param alterRequest 表变更请求
* @return 是否成功
*/
private boolean executeRollback(TableAlterRequest alterRequest) {
try {
// 实现回滚逻辑
// 这里可以根据具体的变更类型实现相应的回滚操作

return true;

} catch (Exception e) {
logger.error("执行回滚操作异常,变更ID: {}", alterRequest.getAlterId(), e);
return false;
}
}

/**
* 构建pt-online-schema-change命令
* @param alterRequest 表变更请求
* @return 命令字符串
*/
private String buildPtOnlineSchemaChangeCommand(TableAlterRequest alterRequest) {
StringBuilder command = new StringBuilder();
command.append(properties.getPtOnlineSchemaChangePath());
command.append(" --alter='").append(alterRequest.getAlterSql()).append("'");
command.append(" --host=").append(extractHostFromUrl(properties.getDatabase().getUrl()));
command.append(" --port=").append(extractPortFromUrl(properties.getDatabase().getUrl()));
command.append(" --user=").append(properties.getDatabase().getUsername());
command.append(" --password=").append(properties.getDatabase().getPassword());
command.append(" --database=").append(alterRequest.getDatabaseName());
command.append(" --table=").append(alterRequest.getTableName());
command.append(" --execute");

return command.toString();
}

/**
* 构建gh-ost命令
* @param alterRequest 表变更请求
* @return 命令字符串
*/
private String buildGhOstCommand(TableAlterRequest alterRequest) {
StringBuilder command = new StringBuilder();
command.append(properties.getGhOstPath());
command.append(" --alter='").append(alterRequest.getAlterSql()).append("'");
command.append(" --host=").append(extractHostFromUrl(properties.getDatabase().getUrl()));
command.append(" --port=").append(extractPortFromUrl(properties.getDatabase().getUrl()));
command.append(" --user=").append(properties.getDatabase().getUsername());
command.append(" --password=").append(properties.getDatabase().getPassword());
command.append(" --database=").append(alterRequest.getDatabaseName());
command.append(" --table=").append(alterRequest.getTableName());
command.append(" --execute");

return command.toString();
}

/**
* 从URL中提取主机名
* @param url 数据库URL
* @return 主机名
*/
private String extractHostFromUrl(String url) {
try {
URI uri = new URI(url.replace("jdbc:", ""));
return uri.getHost();
} catch (Exception e) {
return "localhost";
}
}

/**
* 从URL中提取端口
* @param url 数据库URL
* @return 端口
*/
private String extractPortFromUrl(String url) {
try {
URI uri = new URI(url.replace("jdbc:", ""));
return String.valueOf(uri.getPort());
} catch (Exception e) {
return "3306";
}
}
}

2.5 MySQL表变更结果类

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
/**
* MySQL表变更批量结果类
* @author 运维实战
*/
@Data
public class TableAlterBatchResult {

private boolean success;
private int totalCount;
private int successCount;
private int failureCount;
private List<TableAlterResult> results;
private String error;
private long startTime;
private long endTime;

public TableAlterBatchResult() {
this.success = false;
this.successCount = 0;
this.failureCount = 0;
this.results = new ArrayList<>();
}

/**
* 获取变更耗时
* @return 变更耗时(毫秒)
*/
public long getDuration() {
return endTime - startTime;
}

/**
* 是否成功
* @return 是否成功
*/
public boolean isSuccess() {
return success;
}
}

3. 高级功能实现

3.1 MySQL在线DDL服务

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
/**
* MySQL在线DDL服务
* @author 运维实战
*/
@Service
public class MySQLOnlineDDLService {

@Autowired
private MySQLTableAlterProperties properties;

@Autowired
private DataSource dataSource;

@Autowired
private MySQLTableAlterMonitorService mysqlTableAlterMonitorService;

private static final Logger logger = LoggerFactory.getLogger(MySQLOnlineDDLService.class);

/**
* 执行在线DDL
* @param alterRequest 表变更请求
* @return 表变更结果
*/
public TableAlterResult executeOnlineDDL(TableAlterRequest alterRequest) {
logger.info("执行在线DDL,变更ID: {}, 表: {}.{}",
alterRequest.getAlterId(), alterRequest.getDatabaseName(), alterRequest.getTableName());

TableAlterResult result = new TableAlterResult(
alterRequest.getAlterId(), alterRequest.getDatabaseName(),
alterRequest.getTableName(), alterRequest.getAlterType());
result.setStartTime(System.currentTimeMillis());

try {
// 验证在线DDL支持
if (!isOnlineDDLSupported(alterRequest)) {
result.setSuccess(false);
result.setError("不支持在线DDL操作");
result.setEndTime(System.currentTimeMillis());
return result;
}

// 构建在线DDL SQL
String onlineDDLSql = buildOnlineDDLSql(alterRequest);

// 执行在线DDL
boolean success = executeOnlineDDLSql(onlineDDLSql, alterRequest);

if (success) {
result.setSuccess(true);
result.setStatus("COMPLETED");
result.setEndTime(System.currentTimeMillis());

logger.info("在线DDL执行成功,变更ID: {}, 表: {}.{}, 耗时: {}ms",
alterRequest.getAlterId(), alterRequest.getDatabaseName(),
alterRequest.getTableName(), result.getDuration());
} else {
result.setSuccess(false);
result.setStatus("FAILED");
result.setError("在线DDL执行失败");
result.setEndTime(System.currentTimeMillis());
}

return result;

} catch (Exception e) {
logger.error("在线DDL执行异常,变更ID: {}", alterRequest.getAlterId(), e);
result.setSuccess(false);
result.setStatus("FAILED");
result.setError("在线DDL执行异常: " + e.getMessage());
result.setEndTime(System.currentTimeMillis());
return result;
}
}

/**
* 检查在线DDL支持
* @param alterRequest 表变更请求
* @return 是否支持
*/
private boolean isOnlineDDLSupported(TableAlterRequest alterRequest) {
try {
// 检查MySQL版本
String version = getMySQLVersion();
if (version == null || !version.startsWith("5.6")) {
return false;
}

// 检查变更类型支持
String alterType = alterRequest.getAlterType();
switch (alterType.toUpperCase()) {
case "ADD_COLUMN":
case "DROP_COLUMN":
case "MODIFY_COLUMN":
case "ADD_INDEX":
case "DROP_INDEX":
return true;
default:
return false;
}

} catch (Exception e) {
logger.error("检查在线DDL支持异常", e);
return false;
}
}

/**
* 构建在线DDL SQL
* @param alterRequest 表变更请求
* @return 在线DDL SQL
*/
private String buildOnlineDDLSql(TableAlterRequest alterRequest) {
StringBuilder sql = new StringBuilder();
sql.append("ALTER TABLE ").append(alterRequest.getDatabaseName())
.append(".").append(alterRequest.getTableName());
sql.append(" ").append(alterRequest.getAlterSql());
sql.append(" ALGORITHM=INPLACE, LOCK=NONE");

return sql.toString();
}

/**
* 执行在线DDL SQL
* @param sql 在线DDL SQL
* @param alterRequest 表变更请求
* @return 是否成功
*/
private boolean executeOnlineDDLSql(String sql, TableAlterRequest alterRequest) {
try (Connection connection = dataSource.getConnection()) {
// 设置超时时间
connection.setNetworkTimeout(null, (int) properties.getOnlineDDLTimeout() * 1000);

// 执行SQL
try (Statement statement = connection.createStatement()) {
statement.setQueryTimeout((int) properties.getOnlineDDLTimeout());
statement.execute(sql);

logger.info("在线DDL SQL执行成功,变更ID: {}, SQL: {}", alterRequest.getAlterId(), sql);
return true;
}

} catch (Exception e) {
logger.error("在线DDL SQL执行异常,变更ID: {}, SQL: {}", alterRequest.getAlterId(), sql, e);
return false;
}
}

/**
* 获取MySQL版本
* @return MySQL版本
*/
private String getMySQLVersion() {
try (Connection connection = dataSource.getConnection()) {
try (Statement statement = connection.createStatement()) {
try (ResultSet resultSet = statement.executeQuery("SELECT VERSION()")) {
if (resultSet.next()) {
return resultSet.getString(1);
}
}
}
} catch (Exception e) {
logger.error("获取MySQL版本异常", e);
}
return null;
}
}

3.2 MySQL表结构服务

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
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
/**
* MySQL表结构服务
* @author 运维实战
*/
@Service
public class MySQLTableStructureService {

@Autowired
private DataSource dataSource;

@Autowired
private MySQLTableAlterMonitorService mysqlTableAlterMonitorService;

private static final Logger logger = LoggerFactory.getLogger(MySQLTableStructureService.class);

/**
* 获取表结构信息
* @param databaseName 数据库名称
* @param tableName 表名称
* @return 表结构信息
*/
public TableStructureInfo getTableStructure(String databaseName, String tableName) {
logger.info("获取表结构信息,表: {}.{}", databaseName, tableName);

try {
TableStructureInfo tableStructure = new TableStructureInfo(databaseName, tableName);

// 获取表基本信息
getTableBasicInfo(tableStructure);

// 获取字段信息
getTableColumns(tableStructure);

// 获取索引信息
getTableIndexes(tableStructure);

logger.info("获取表结构信息成功,表: {}.{}, 字段数: {}, 索引数: {}",
databaseName, tableName, tableStructure.getColumns().size(), tableStructure.getIndexes().size());

return tableStructure;

} catch (Exception e) {
logger.error("获取表结构信息异常,表: {}.{}", databaseName, tableName, e);
return null;
}
}

/**
* 获取表基本信息
* @param tableStructure 表结构信息
*/
private void getTableBasicInfo(TableStructureInfo tableStructure) {
try (Connection connection = dataSource.getConnection()) {
String sql = "SELECT ENGINE, TABLE_COLLATION, TABLE_ROWS, " +
"DATA_LENGTH, INDEX_LENGTH, CREATE_TIME, UPDATE_TIME " +
"FROM INFORMATION_SCHEMA.TABLES " +
"WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?";

try (PreparedStatement statement = connection.prepareStatement(sql)) {
statement.setString(1, tableStructure.getDatabaseName());
statement.setString(2, tableStructure.getTableName());

try (ResultSet resultSet = statement.executeQuery()) {
if (resultSet.next()) {
tableStructure.setEngine(resultSet.getString("ENGINE"));
tableStructure.setCollation(resultSet.getString("TABLE_COLLATION"));
tableStructure.setRowCount(resultSet.getLong("TABLE_ROWS"));
tableStructure.setTableSize(resultSet.getLong("DATA_LENGTH"));
tableStructure.setIndexSize(resultSet.getLong("INDEX_LENGTH"));
tableStructure.setCreateTime(resultSet.getTimestamp("CREATE_TIME").getTime());
tableStructure.setUpdateTime(resultSet.getTimestamp("UPDATE_TIME").getTime());
}
}
}

} catch (Exception e) {
logger.error("获取表基本信息异常,表: {}.{}",
tableStructure.getDatabaseName(), tableStructure.getTableName(), e);
}
}

/**
* 获取表字段信息
* @param tableStructure 表结构信息
*/
private void getTableColumns(TableStructureInfo tableStructure) {
try (Connection connection = dataSource.getConnection()) {
String sql = "SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, " +
"COLUMN_DEFAULT, COLUMN_COMMENT, ORDINAL_POSITION " +
"FROM INFORMATION_SCHEMA.COLUMNS " +
"WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? " +
"ORDER BY ORDINAL_POSITION";

try (PreparedStatement statement = connection.prepareStatement(sql)) {
statement.setString(1, tableStructure.getDatabaseName());
statement.setString(2, tableStructure.getTableName());

try (ResultSet resultSet = statement.executeQuery()) {
while (resultSet.next()) {
ColumnInfo columnInfo = new ColumnInfo();
columnInfo.setColumnName(resultSet.getString("COLUMN_NAME"));
columnInfo.setColumnType(resultSet.getString("COLUMN_TYPE"));
columnInfo.setNullable("YES".equals(resultSet.getString("IS_NULLABLE")));
columnInfo.setDefaultValue(resultSet.getString("COLUMN_DEFAULT"));
columnInfo.setComment(resultSet.getString("COLUMN_COMMENT"));
columnInfo.setPosition(resultSet.getInt("ORDINAL_POSITION"));

tableStructure.addColumn(columnInfo);
}
}
}

} catch (Exception e) {
logger.error("获取表字段信息异常,表: {}.{}",
tableStructure.getDatabaseName(), tableStructure.getTableName(), e);
}
}

/**
* 获取表索引信息
* @param tableStructure 表结构信息
*/
private void getTableIndexes(TableStructureInfo tableStructure) {
try (Connection connection = dataSource.getConnection()) {
String sql = "SELECT INDEX_NAME, INDEX_TYPE, COLUMN_NAME, NON_UNIQUE " +
"FROM INFORMATION_SCHEMA.STATISTICS " +
"WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? " +
"ORDER BY INDEX_NAME, SEQ_IN_INDEX";

try (PreparedStatement statement = connection.prepareStatement(sql)) {
statement.setString(1, tableStructure.getDatabaseName());
statement.setString(2, tableStructure.getTableName());

try (ResultSet resultSet = statement.executeQuery()) {
Map<String, IndexInfo> indexMap = new HashMap<>();

while (resultSet.next()) {
String indexName = resultSet.getString("INDEX_NAME");
IndexInfo indexInfo = indexMap.get(indexName);

if (indexInfo == null) {
indexInfo = new IndexInfo();
indexInfo.setIndexName(indexName);
indexInfo.setIndexType(resultSet.getString("INDEX_TYPE"));
indexInfo.setUnique(resultSet.getInt("NON_UNIQUE") == 0);
indexInfo.setColumns(new ArrayList<>());
indexMap.put(indexName, indexInfo);
}

indexInfo.getColumns().add(resultSet.getString("COLUMN_NAME"));
}

// 添加索引信息到表结构
for (IndexInfo indexInfo : indexMap.values()) {
tableStructure.addIndex(indexInfo);
}
}
}

} catch (Exception e) {
logger.error("获取表索引信息异常,表: {}.{}",
tableStructure.getDatabaseName(), tableStructure.getTableName(), e);
}
}

/**
* 验证表结构变更
* @param tableStructure 表结构信息
* @param alterRequest 表变更请求
* @return 是否有效
*/
public boolean validateTableStructureAlter(TableStructureInfo tableStructure, TableAlterRequest alterRequest) {
try {
// 验证表是否存在
if (tableStructure == null) {
return false;
}

// 验证变更类型
String alterType = alterRequest.getAlterType();
switch (alterType.toUpperCase()) {
case "ADD_COLUMN":
return validateAddColumn(tableStructure, alterRequest);
case "DROP_COLUMN":
return validateDropColumn(tableStructure, alterRequest);
case "MODIFY_COLUMN":
return validateModifyColumn(tableStructure, alterRequest);
case "ADD_INDEX":
return validateAddIndex(tableStructure, alterRequest);
case "DROP_INDEX":
return validateDropIndex(tableStructure, alterRequest);
default:
return false;
}

} catch (Exception e) {
logger.error("验证表结构变更异常", e);
return false;
}
}

/**
* 验证添加字段
* @param tableStructure 表结构信息
* @param alterRequest 表变更请求
* @return 是否有效
*/
private boolean validateAddColumn(TableStructureInfo tableStructure, TableAlterRequest alterRequest) {
// 检查字段是否已存在
String columnName = extractColumnNameFromAlterSql(alterRequest.getAlterSql());
if (columnName != null) {
for (ColumnInfo column : tableStructure.getColumns()) {
if (columnName.equals(column.getColumnName())) {
return false;
}
}
}
return true;
}

/**
* 验证删除字段
* @param tableStructure 表结构信息
* @param alterRequest 表变更请求
* @return 是否有效
*/
private boolean validateDropColumn(TableStructureInfo tableStructure, TableAlterRequest alterRequest) {
// 检查字段是否存在
String columnName = extractColumnNameFromAlterSql(alterRequest.getAlterSql());
if (columnName != null) {
for (ColumnInfo column : tableStructure.getColumns()) {
if (columnName.equals(column.getColumnName())) {
return true;
}
}
}
return false;
}

/**
* 验证修改字段
* @param tableStructure 表结构信息
* @param alterRequest 表变更请求
* @return 是否有效
*/
private boolean validateModifyColumn(TableStructureInfo tableStructure, TableAlterRequest alterRequest) {
// 检查字段是否存在
String columnName = extractColumnNameFromAlterSql(alterRequest.getAlterSql());
if (columnName != null) {
for (ColumnInfo column : tableStructure.getColumns()) {
if (columnName.equals(column.getColumnName())) {
return true;
}
}
}
return false;
}

/**
* 验证添加索引
* @param tableStructure 表结构信息
* @param alterRequest 表变更请求
* @return 是否有效
*/
private boolean validateAddIndex(TableStructureInfo tableStructure, TableAlterRequest alterRequest) {
// 检查索引是否已存在
String indexName = extractIndexNameFromAlterSql(alterRequest.getAlterSql());
if (indexName != null) {
for (IndexInfo index : tableStructure.getIndexes()) {
if (indexName.equals(index.getIndexName())) {
return false;
}
}
}
return true;
}

/**
* 验证删除索引
* @param tableStructure 表结构信息
* @param alterRequest 表变更请求
* @return 是否有效
*/
private boolean validateDropIndex(TableStructureInfo tableStructure, TableAlterRequest alterRequest) {
// 检查索引是否存在
String indexName = extractIndexNameFromAlterSql(alterRequest.getAlterSql());
if (indexName != null) {
for (IndexInfo index : tableStructure.getIndexes()) {
if (indexName.equals(index.getIndexName())) {
return true;
}
}
}
return false;
}

/**
* 从ALTER SQL中提取字段名
* @param alterSql ALTER SQL
* @return 字段名
*/
private String extractColumnNameFromAlterSql(String alterSql) {
try {
// 简单的字段名提取逻辑
// 这里可以根据具体的SQL格式实现更复杂的解析
if (alterSql.toUpperCase().contains("ADD COLUMN")) {
String[] parts = alterSql.split("ADD COLUMN");
if (parts.length > 1) {
String columnPart = parts[1].trim();
String[] columnParts = columnPart.split("\\s+");
if (columnParts.length > 0) {
return columnParts[0];
}
}
} else if (alterSql.toUpperCase().contains("DROP COLUMN")) {
String[] parts = alterSql.split("DROP COLUMN");
if (parts.length > 1) {
String columnPart = parts[1].trim();
String[] columnParts = columnPart.split("\\s+");
if (columnParts.length > 0) {
return columnParts[0];
}
}
} else if (alterSql.toUpperCase().contains("MODIFY COLUMN")) {
String[] parts = alterSql.split("MODIFY COLUMN");
if (parts.length > 1) {
String columnPart = parts[1].trim();
String[] columnParts = columnPart.split("\\s+");
if (columnParts.length > 0) {
return columnParts[0];
}
}
}
} catch (Exception e) {
logger.error("提取字段名异常,SQL: {}", alterSql, e);
}
return null;
}

/**
* 从ALTER SQL中提取索引名
* @param alterSql ALTER SQL
* @return 索引名
*/
private String extractIndexNameFromAlterSql(String alterSql) {
try {
// 简单的索引名提取逻辑
if (alterSql.toUpperCase().contains("ADD INDEX")) {
String[] parts = alterSql.split("ADD INDEX");
if (parts.length > 1) {
String indexPart = parts[1].trim();
String[] indexParts = indexPart.split("\\s+");
if (indexParts.length > 0) {
return indexParts[0];
}
}
} else if (alterSql.toUpperCase().contains("DROP INDEX")) {
String[] parts = alterSql.split("DROP INDEX");
if (parts.length > 1) {
String indexPart = parts[1].trim();
String[] indexParts = indexPart.split("\\s+");
if (indexParts.length > 0) {
return indexParts[0];
}
}
}
} catch (Exception e) {
logger.error("提取索引名异常,SQL: {}", alterSql, e);
}
return null;
}
}

3.3 MySQL数据迁移服务

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
/**
* MySQL数据迁移服务
* @author 运维实战
*/
@Service
public class MySQLDataMigrationService {

@Autowired
private MySQLTableAlterProperties properties;

@Autowired
private DataSource dataSource;

@Autowired
private MySQLTableAlterMonitorService mysqlTableAlterMonitorService;

private static final Logger logger = LoggerFactory.getLogger(MySQLDataMigrationService.class);

/**
* 执行数据迁移
* @param alterRequest 表变更请求
* @return 数据迁移结果
*/
public DataMigrationResult executeDataMigration(TableAlterRequest alterRequest) {
logger.info("执行数据迁移,变更ID: {}, 表: {}.{}",
alterRequest.getAlterId(), alterRequest.getDatabaseName(), alterRequest.getTableName());

DataMigrationResult result = new DataMigrationResult();
result.setAlterId(alterRequest.getAlterId());
result.setDatabaseName(alterRequest.getDatabaseName());
result.setTableName(alterRequest.getTableName());
result.setStartTime(System.currentTimeMillis());

try {
// 检查是否启用数据迁移
if (!properties.isEnableDataMigration()) {
result.setSuccess(false);
result.setError("数据迁移功能未启用");
result.setEndTime(System.currentTimeMillis());
return result;
}

// 执行数据迁移
boolean success = performDataMigration(alterRequest, result);

if (success) {
result.setSuccess(true);
result.setStatus("COMPLETED");
result.setEndTime(System.currentTimeMillis());

logger.info("数据迁移成功,变更ID: {}, 表: {}.{}, 迁移记录数: {}, 耗时: {}ms",
alterRequest.getAlterId(), alterRequest.getDatabaseName(),
alterRequest.getTableName(), result.getMigratedRecords(), result.getDuration());
} else {
result.setSuccess(false);
result.setStatus("FAILED");
result.setError("数据迁移失败");
result.setEndTime(System.currentTimeMillis());
}

return result;

} catch (Exception e) {
logger.error("数据迁移异常,变更ID: {}", alterRequest.getAlterId(), e);
result.setSuccess(false);
result.setStatus("FAILED");
result.setError("数据迁移异常: " + e.getMessage());
result.setEndTime(System.currentTimeMillis());
return result;
}
}

/**
* 执行数据迁移
* @param alterRequest 表变更请求
* @param result 数据迁移结果
* @return 是否成功
*/
private boolean performDataMigration(TableAlterRequest alterRequest, DataMigrationResult result) {
try {
// 获取表总记录数
long totalRecords = getTableRecordCount(alterRequest.getDatabaseName(), alterRequest.getTableName());
result.setTotalRecords(totalRecords);

if (totalRecords == 0) {
result.setMigratedRecords(0);
return true;
}

// 分批迁移数据
long migratedRecords = 0;
int batchSize = properties.getDataMigrationBatchSize();
long offset = 0;

while (offset < totalRecords) {
try {
// 迁移一批数据
int batchMigrated = migrateBatch(alterRequest, offset, batchSize);
migratedRecords += batchMigrated;

// 更新进度
result.setMigratedRecords(migratedRecords);
result.setProgress((double) migratedRecords / totalRecords * 100);

// 休眠一段时间
Thread.sleep(properties.getDataMigrationInterval());

offset += batchSize;

} catch (Exception e) {
logger.error("批量数据迁移异常,变更ID: {}, 偏移量: {}", alterRequest.getAlterId(), offset, e);
result.setFailedBatches(result.getFailedBatches() + 1);

// 检查失败批次是否超过阈值
if (result.getFailedBatches() > properties.getDataMigrationFailureAlertThreshold()) {
return false;
}
}
}

return true;

} catch (Exception e) {
logger.error("执行数据迁移异常,变更ID: {}", alterRequest.getAlterId(), e);
return false;
}
}

/**
* 获取表记录数
* @param databaseName 数据库名称
* @param tableName 表名称
* @return 记录数
*/
private long getTableRecordCount(String databaseName, String tableName) {
try (Connection connection = dataSource.getConnection()) {
String sql = "SELECT COUNT(*) FROM " + databaseName + "." + tableName;

try (Statement statement = connection.createStatement()) {
try (ResultSet resultSet = statement.executeQuery(sql)) {
if (resultSet.next()) {
return resultSet.getLong(1);
}
}
}

} catch (Exception e) {
logger.error("获取表记录数异常,表: {}.{}", databaseName, tableName, e);
}
return 0;
}

/**
* 迁移一批数据
* @param alterRequest 表变更请求
* @param offset 偏移量
* @param batchSize 批次大小
* @return 迁移记录数
*/
private int migrateBatch(TableAlterRequest alterRequest, long offset, int batchSize) {
try (Connection connection = dataSource.getConnection()) {
// 构建查询SQL
String selectSql = "SELECT * FROM " + alterRequest.getDatabaseName() + "." + alterRequest.getTableName() +
" LIMIT " + batchSize + " OFFSET " + offset;

// 构建插入SQL
String insertSql = buildInsertSql(alterRequest);

try (PreparedStatement selectStatement = connection.prepareStatement(selectSql);
PreparedStatement insertStatement = connection.prepareStatement(insertSql)) {

try (ResultSet resultSet = selectStatement.executeQuery()) {
int count = 0;

while (resultSet.next()) {
// 设置插入参数
setInsertParameters(insertStatement, resultSet, alterRequest);

// 执行插入
insertStatement.executeUpdate();
count++;
}

return count;
}
}

} catch (Exception e) {
logger.error("迁移批量数据异常,变更ID: {}, 偏移量: {}", alterRequest.getAlterId(), offset, e);
return 0;
}
}

/**
* 构建插入SQL
* @param alterRequest 表变更请求
* @return 插入SQL
*/
private String buildInsertSql(TableAlterRequest alterRequest) {
// 根据变更类型构建相应的插入SQL
// 这里需要根据具体的变更类型实现
return "INSERT INTO " + alterRequest.getDatabaseName() + "." + alterRequest.getTableName() + " VALUES (?)";
}

/**
* 设置插入参数
* @param statement 插入语句
* @param resultSet 查询结果集
* @param alterRequest 表变更请求
*/
private void setInsertParameters(PreparedStatement statement, ResultSet resultSet, TableAlterRequest alterRequest) {
try {
// 根据变更类型设置相应的参数
// 这里需要根据具体的变更类型实现
statement.setObject(1, resultSet.getObject(1));
} catch (Exception e) {
logger.error("设置插入参数异常,变更ID: {}", alterRequest.getAlterId(), e);
}
}
}

/**
* 数据迁移结果类
* @author 运维实战
*/
@Data
public class DataMigrationResult {

private boolean success;
private String alterId;
private String databaseName;
private String tableName;
private String status;
private long totalRecords;
private long migratedRecords;
private double progress;
private int failedBatches;
private String error;
private long startTime;
private long endTime;

public DataMigrationResult() {
this.success = false;
this.migratedRecords = 0;
this.progress = 0.0;
this.failedBatches = 0;
}

/**
* 获取迁移耗时
* @return 迁移耗时(毫秒)
*/
public long getDuration() {
return endTime - startTime;
}

/**
* 是否成功
* @return 是否成功
*/
public boolean isSuccess() {
return success;
}
}

3.4 MySQL表变更监控服务

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
/**
* MySQL表变更监控服务
* @author 运维实战
*/
@Service
public class MySQLTableAlterMonitorService {

private final AtomicLong totalTableAlters = new AtomicLong(0);
private final AtomicLong totalTableAltersSuccessful = new AtomicLong(0);
private final AtomicLong totalTableAltersFailed = new AtomicLong(0);
private final AtomicLong totalDataMigrations = new AtomicLong(0);
private final AtomicLong totalDataMigrationsSuccessful = new AtomicLong(0);
private final AtomicLong totalDataMigrationsFailed = new AtomicLong(0);
private final AtomicLong totalBatchAlters = new AtomicLong(0);
private final AtomicLong totalBatchAltersSuccessful = new AtomicLong(0);

private long lastResetTime = System.currentTimeMillis();
private final long resetInterval = 300000; // 5分钟重置一次

private static final Logger logger = LoggerFactory.getLogger(MySQLTableAlterMonitorService.class);

/**
* 记录表变更
* @param tableName 表名称
* @param success 是否成功
*/
public void recordTableAlter(String tableName, boolean success) {
totalTableAlters.incrementAndGet();

if (success) {
totalTableAltersSuccessful.incrementAndGet();
} else {
totalTableAltersFailed.incrementAndGet();
}

logger.debug("记录表变更: 表名={}, 成功={}", tableName, success);
}

/**
* 记录数据迁移
* @param tableName 表名称
* @param success 是否成功
*/
public void recordDataMigration(String tableName, boolean success) {
totalDataMigrations.incrementAndGet();

if (success) {
totalDataMigrationsSuccessful.incrementAndGet();
} else {
totalDataMigrationsFailed.incrementAndGet();
}

logger.debug("记录数据迁移: 表名={}, 成功={}", tableName, success);
}

/**
* 记录批量表变更
* @param totalCount 总数量
* @param successCount 成功数量
*/
public void recordTableAlterBatch(int totalCount, int successCount) {
totalBatchAlters.addAndGet(totalCount);
totalBatchAltersSuccessful.addAndGet(successCount);
totalTableAlters.addAndGet(totalCount);
totalTableAltersSuccessful.addAndGet(successCount);
totalTableAltersFailed.addAndGet(totalCount - successCount);

logger.debug("记录批量表变更: 总数={}, 成功={}", totalCount, successCount);
}

/**
* 获取监控指标
* @return 监控指标
*/
public MySQLTableAlterMetrics getMetrics() {
// 检查是否需要重置
if (System.currentTimeMillis() - lastResetTime > resetInterval) {
resetMetrics();
}

MySQLTableAlterMetrics metrics = new MySQLTableAlterMetrics();
metrics.setTotalTableAlters(totalTableAlters.get());
metrics.setTotalTableAltersSuccessful(totalTableAltersSuccessful.get());
metrics.setTotalTableAltersFailed(totalTableAltersFailed.get());
metrics.setTotalDataMigrations(totalDataMigrations.get());
metrics.setTotalDataMigrationsSuccessful(totalDataMigrationsSuccessful.get());
metrics.setTotalDataMigrationsFailed(totalDataMigrationsFailed.get());
metrics.setTotalBatchAlters(totalBatchAlters.get());
metrics.setTotalBatchAltersSuccessful(totalBatchAltersSuccessful.get());
metrics.setTimestamp(System.currentTimeMillis());

return metrics;
}

/**
* 重置指标
*/
private void resetMetrics() {
totalTableAlters.set(0);
totalTableAltersSuccessful.set(0);
totalTableAltersFailed.set(0);
totalDataMigrations.set(0);
totalDataMigrationsSuccessful.set(0);
totalDataMigrationsFailed.set(0);
totalBatchAlters.set(0);
totalBatchAltersSuccessful.set(0);
lastResetTime = System.currentTimeMillis();

logger.info("MySQL表变更监控指标重置");
}

/**
* 定期监控MySQL表变更状态
*/
@Scheduled(fixedRate = 30000) // 每30秒监控一次
public void monitorMySQLTableAlterStatus() {
try {
MySQLTableAlterMetrics metrics = getMetrics();

logger.info("MySQL表变更监控: 表变更={}, 表变更成功={}, 表变更失败={}, 数据迁移={}, 数据迁移成功={}, 数据迁移失败={}, 批量变更={}, 批量成功={}, 表变更成功率={}%, 数据迁移成功率={}%",
metrics.getTotalTableAlters(), metrics.getTotalTableAltersSuccessful(), metrics.getTotalTableAltersFailed(),
metrics.getTotalDataMigrations(), metrics.getTotalDataMigrationsSuccessful(), metrics.getTotalDataMigrationsFailed(),
metrics.getTotalBatchAlters(), metrics.getTotalBatchAltersSuccessful(),
String.format("%.2f", metrics.getTableAlterSuccessRate()),
String.format("%.2f", metrics.getDataMigrationSuccessRate()));

// 检查异常情况
if (metrics.getTableAlterSuccessRate() < 95) {
logger.warn("MySQL表变更成功率过低: {}%", String.format("%.2f", metrics.getTableAlterSuccessRate()));
}

if (metrics.getDataMigrationSuccessRate() < 90) {
logger.warn("MySQL数据迁移成功率过低: {}%", String.format("%.2f", metrics.getDataMigrationSuccessRate()));
}

} catch (Exception e) {
logger.error("MySQL表变更状态监控失败", e);
}
}
}

3.5 MySQL表变更指标类

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
/**
* MySQL表变更指标类
* @author 运维实战
*/
@Data
public class MySQLTableAlterMetrics {

private long totalTableAlters;
private long totalTableAltersSuccessful;
private long totalTableAltersFailed;
private long totalDataMigrations;
private long totalDataMigrationsSuccessful;
private long totalDataMigrationsFailed;
private long totalBatchAlters;
private long totalBatchAltersSuccessful;
private long timestamp;

public MySQLTableAlterMetrics() {
this.timestamp = System.currentTimeMillis();
}

/**
* 获取表变更成功率
* @return 表变更成功率
*/
public double getTableAlterSuccessRate() {
long total = totalTableAltersSuccessful + totalTableAltersFailed;
if (total == 0) return 0.0;
return (double) totalTableAltersSuccessful / total * 100;
}

/**
* 获取表变更失败率
* @return 表变更失败率
*/
public double getTableAlterFailureRate() {
long total = totalTableAltersSuccessful + totalTableAltersFailed;
if (total == 0) return 0.0;
return (double) totalTableAltersFailed / total * 100;
}

/**
* 获取数据迁移成功率
* @return 数据迁移成功率
*/
public double getDataMigrationSuccessRate() {
long total = totalDataMigrationsSuccessful + totalDataMigrationsFailed;
if (total == 0) return 0.0;
return (double) totalDataMigrationsSuccessful / total * 100;
}

/**
* 获取数据迁移失败率
* @return 数据迁移失败率
*/
public double getDataMigrationFailureRate() {
long total = totalDataMigrationsSuccessful + totalDataMigrationsFailed;
if (total == 0) return 0.0;
return (double) totalDataMigrationsFailed / total * 100;
}

/**
* 获取表变更效率
* @return 表变更效率
*/
public double getTableAlterEfficiency() {
if (totalTableAlters == 0) return 0.0;
return (double) totalTableAltersSuccessful / totalTableAlters * 100;
}

/**
* 获取数据迁移效率
* @return 数据迁移效率
*/
public double getDataMigrationEfficiency() {
if (totalDataMigrations == 0) return 0.0;
return (double) totalDataMigrationsSuccessful / totalDataMigrations * 100;
}

/**
* 是否健康
* @return 是否健康
*/
public boolean isHealthy() {
return getTableAlterSuccessRate() > 95 &&
getDataMigrationSuccessRate() > 90;
}
}

4. MySQL表变更控制器

4.1 MySQL表变更REST控制器

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
/**
* MySQL表变更REST控制器
* @author 运维实战
*/
@RestController
@RequestMapping("/api/mysql/table-alter")
public class MySQLTableAlterController {

@Autowired
private MySQLTableAlterService mysqlTableAlterService;

@Autowired
private MySQLTableAlterMonitorService mysqlTableAlterMonitorService;

private static final Logger logger = LoggerFactory.getLogger(MySQLTableAlterController.class);

/**
* 执行表变更
* @param alterRequest 表变更请求
* @return 表变更结果
*/
@PostMapping("/execute")
public ResponseEntity<TableAlterResult> executeTableAlter(@RequestBody TableAlterRequest alterRequest) {
try {
logger.info("接收到表变更请求,变更ID: {}", alterRequest.getAlterId());

TableAlterResult result = mysqlTableAlterService.executeTableAlter(alterRequest);

return ResponseEntity.ok(result);

} catch (Exception e) {
logger.error("表变更失败", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}

/**
* 批量执行表变更
* @param alterRequests 表变更请求列表
* @return 批量表变更结果
*/
@PostMapping("/batch-execute")
public ResponseEntity<TableAlterBatchResult> batchExecuteTableAlter(@RequestBody List<TableAlterRequest> alterRequests) {
try {
logger.info("接收到批量表变更请求,数量: {}", alterRequests.size());

TableAlterBatchResult result = mysqlTableAlterService.batchExecuteTableAlter(alterRequests);

return ResponseEntity.ok(result);

} catch (Exception e) {
logger.error("批量表变更失败", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}

/**
* 获取表结构信息
* @param databaseName 数据库名称
* @param tableName 表名称
* @return 表结构信息
*/
@GetMapping("/table-structure/{databaseName}/{tableName}")
public ResponseEntity<TableStructureInfo> getTableStructure(
@PathVariable String databaseName,
@PathVariable String tableName) {
try {
logger.info("接收到获取表结构信息请求,表: {}.{}", databaseName, tableName);

TableStructureInfo tableStructure = mysqlTableAlterService.getTableStructure(databaseName, tableName);

return ResponseEntity.ok(tableStructure);

} catch (Exception e) {
logger.error("获取表结构信息失败", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}

/**
* 回滚表变更
* @param alterRequest 表变更请求
* @return 回滚结果
*/
@PostMapping("/rollback")
public ResponseEntity<TableAlterResult> rollbackTableAlter(@RequestBody TableAlterRequest alterRequest) {
try {
logger.info("接收到表变更回滚请求,变更ID: {}", alterRequest.getAlterId());

TableAlterResult result = mysqlTableAlterService.rollbackTableAlter(alterRequest);

return ResponseEntity.ok(result);

} catch (Exception e) {
logger.error("表变更回滚失败", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}

/**
* 获取MySQL表变更监控指标
* @return 监控指标
*/
@GetMapping("/metrics")
public ResponseEntity<MySQLTableAlterMetrics> getMySQLTableAlterMetrics() {
try {
MySQLTableAlterMetrics metrics = mysqlTableAlterMonitorService.getMetrics();
return ResponseEntity.ok(metrics);
} catch (Exception e) {
logger.error("获取MySQL表变更监控指标失败", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
}

5. 总结

5.1 MySQL千万级表变更字段最佳实践

  1. 合理选择变更策略: 根据表大小和业务需求选择合适的变更策略
  2. 在线DDL优先: 优先使用MySQL在线DDL功能
  3. 工具辅助: 使用pt-online-schema-change或gh-ost等工具
  4. 分批处理: 对于大数据量表采用分批处理策略
  5. 监控告警: 实时监控表变更过程和性能

5.2 性能优化建议

  • 变更策略优化: 根据表大小选择合适的变更策略
  • 分批处理: 使用分批处理减少锁表时间
  • 索引优化: 合理设计索引减少变更影响
  • 资源监控: 监控CPU、内存、磁盘IO等资源使用
  • 错误处理: 完善错误处理和重试机制

5.3 运维管理要点

  • 实时监控: 监控表变更状态和性能
  • 变更管理: 建立完善的变更管理和审批流程
  • 备份策略: 变更前做好数据备份
  • 回滚机制: 建立完善的回滚机制
  • 性能调优: 根据监控数据优化表变更性能

通过本文的MySQL千万级表变更字段不锁表Java实战指南,您可以掌握MySQL表结构变更的原理、实现方法、性能优化技巧以及在企业级应用中的最佳实践,构建高效、可靠的MySQL表变更系统!