表加字段如何不停机
执行修改语句前,先执行这些,看是否存在阻塞的语句。
– 查询 正在执行的事务: trx_mysql_thread_id 定位用户名
SELECT * FROM information_schema.INNODB_TRX;
– 查看正在锁的事务
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS;
– 查看等待锁的事务
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS;
– 执行how full processlist 查看用户
没有并发的情况下:
200w的表,加一个普通int字段,需要44s.
60w的表,几分钟一个调用的情况下,需要30s.
综上,实际的耗时,要看表数据量、字段。如果是几千万的数据,并发不大的情况下,加一个字段,需要几分钟。
咱只是一个开发,不是专门的运维。其他的方法对于我来说成本太高。
下面就是一个拦截器,做到了不停机增加表字段的代码。
只拦截insert语句,如果在nacos中配置了要修改的表名target、以及备份表bak,那么就会把数据存到bak表里面。
因为数据会存到两个表,需要自己判断是不是会影响业务。
我们要做的就是:
- 代码上线
- 从target 复制表结构为一个bak表。然后在nacos配置中,配置好两个表名:target和bak
- 这时数据已经开始进入bak表了
- 等待几分钟,确保所有机器都收到了nacos的更新
- 开始给target加表字段
- 删除nacos的配置
- 等待几分钟后,将bak表的数据存入到targert表
- 删除bak表
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.insert.Insert;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.springframework.stereotype.Component;import javax.annotation.Resource;
import java.util.Properties;/*** 加表字段时,配合nacos做到不停机*/
@Intercepts({@Signature(type = Executor.class,method = "update",args = {MappedStatement.class, Object.class})
})
@Component
public class InsertTableReplaceInterceptor implements Interceptor {// 这里就是nacos动态配置@Resourceprivate ProductReturnStructConfig productReturnStructConfig;@Overridepublic Object intercept(Invocation invocation) throws Throwable {MappedStatement ms = (MappedStatement) invocation.getArgs()[0];// 仅处理INSERT语句if (ms.getSqlCommandType() != SqlCommandType.INSERT) {return invocation.proceed();}if (StringUtils.isBlank(productReturnStructConfig.getTargetTable())) {return invocation.proceed();}BoundSql boundSql = ms.getBoundSql(invocation.getArgs()[1]);if (!judgeIsTargetTable(boundSql.getSql())) {// 如果不是特定的表,直接返回return invocation.proceed();}String newSql = replaceInsertTable(boundSql.getSql());// 深度克隆BoundSql并创建新MappedStatementBoundSql newBoundSql = rebuildBoundSql(ms, boundSql, newSql);MappedStatement newMs = rebuildMappedStatement(ms, newBoundSql);// 替换参数Object[] args = invocation.getArgs();args[0] = newMs; // 替换为新的MappedStatementreturn invocation.proceed();}private boolean judgeIsTargetTable(String originalSql) {try {Statement statement = CCJSqlParserUtil.parse(originalSql);if (((Insert) statement).getTable().getFullyQualifiedName().equals(productReturnStructConfig.getTargetTable())) {return true;}} catch (JSQLParserException e) {// 失败返回falsereturn false;}return false;}private BoundSql rebuildBoundSql(MappedStatement ms, BoundSql oldBoundSql, String newSql) {// 创建新的BoundSqlBoundSql newBoundSql = new BoundSql(ms.getConfiguration(),newSql,oldBoundSql.getParameterMappings(),oldBoundSql.getParameterObject());// 复制原始参数值MetaObject oldMeta = SystemMetaObject.forObject(oldBoundSql);MetaObject newMeta = SystemMetaObject.forObject(newBoundSql);newMeta.setValue("metaParameters", oldMeta.getValue("metaParameters"));return newBoundSql;}private MappedStatement rebuildMappedStatement(MappedStatement oldMs, BoundSql newBoundSql) {MappedStatement.Builder builder = new MappedStatement.Builder(oldMs.getConfiguration(),oldMs.getId() + "_Replaced",new SqlSource() {@Overridepublic BoundSql getBoundSql(Object parameterObject) {return newBoundSql;}},oldMs.getSqlCommandType());// 复制其他关键属性builder.resource(oldMs.getResource()).fetchSize(oldMs.getFetchSize()).statementType(oldMs.getStatementType()).keyGenerator(oldMs.getKeyGenerator()).timeout(oldMs.getTimeout()).parameterMap(oldMs.getParameterMap()).resultMaps(oldMs.getResultMaps()).cache(oldMs.getCache());return builder.build();}/*** 专门处理INSERT语句的表名替换*/private String replaceInsertTable(String originalSql) {try {Statement statement = CCJSqlParserUtil.parse(originalSql);if ((statement instanceof Insert)) {Insert insert = (Insert) statement;Table table = insert.getTable();String targetTable = productReturnStructConfig.getBakTable();if (StringUtils.isNotBlank(targetTable)) {table.setName(targetTable);}}return statement.toString();} catch (JSQLParserException e) {return originalSql; // 解析失败保持原SQL}}@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}@Overridepublic void setProperties(Properties properties) {}
}