☰
批量删除 MySQL 同前缀表:information_schema 与 Python 脚本安全实践
2026/10/14 23:54:08 网站建设 项目流程

简介:这是一份面向MySQL数据库管理者的PHP实用工具,用于按指定前缀快速批量删除数据表,特别适合开发或测试环境中的表清理与重置场景。工具以PHP源码形式提供,可在支持PHP的服务器上直接运行,代码结构简单,便于开发者根据实际需求调整前缀匹配规则或增加安全校验。压缩包共含3个文件,其中killsql.php为主脚本,负责连接数据库并执行批量删除;说明.htm提供配置步骤与使用注意事项;另有chinaz.com.txt文本资料,整体压缩包仅3KB,轻量易部署。目前已有198人学习或下载,对需要频繁清理临时表、重建测试数据的开发人员而言,这份工具能显著减少手工操作耗时,降低误删风险,同时可作为学习PHP与MySQL交互的入门参考。

1. 两百张 tmp_ 表摆在面前:批量删除 MySQL 同前缀表 v1.0 到底解决什么

有一类清理工作,放着放着就成了事故现场:应用每跑一轮任务就留下几张tmp_开头或者log_2024_开头的表,半年过去,库里堆了两百多张前缀相同但再也用不上的表。手工DROP TABLE一条条敲,敲到第 50 条耐心就没了;写个循环一把梭,又怕LIKE写宽了把正常业务表一起带走。「批量删除 MySQL 数据库相同前缀的数据表 v1.0」要解决的就是这个场景:给定库名和前缀,自动查出匹配的表清单,确认后分批执行 DROP,并对误删风险做前置拦截。它面向的是手里只有业务库账号、经常被临时表和数据残留折磨的后端开发与运维同学,而不是专职 DBA。下面按这个 v1.0 的完整实现路径来拆解:先对比三条可行路线,再给可复现的 Python 脚本,最后把参数调优、踩坑和验证方法一次说清。

2. 批量删表的三条实现路线:information_schema、SHOW TABLES 与存储过程

删除相同前缀的表,拆开看就两步:先拿到「哪些表命中了前缀」,再对清单执行 DROP。工具靠不靠谱,八成的风险集中在第一步——清单里多一张表,就多一次误删的可能。常见的清单来源有三条:SHOW TABLES LIKE、information_schema.tables视图、存储过程动态拼接。选型时我只看三点:清单能不能过滤视图、返回列结构稳不稳定、执行前能不能看到完整计划。三条路走下来,只有一条扛得住生产环境。

2.1 SHOW TABLES LIKE:能救急,但别让它承担批量删除

SHOW TABLES LIKE是最直观的入口,拿到前缀拼一下就行:

SHOW TABLES FROM my_app LIKE 'tmp\_%';

这条语句会返回所有以tmp_开头的表,优点是不用 JOIN、不用查元数据,连上客户端就能跑。但它有三个明显短板:第一,结果集里分不清表、视图还是临时表,视图一旦进入删除清单,DROP 会把业务维度的逻辑对象一起清掉;第二,拿不到存储引擎、表大小、字符集这类信息,没法做「先删小表再删大表」的排序,也没法评估外键影响面;第三,结果列的命名在不同版本里不一致,早期版本返回的列名就是Tables_in_db,脚本按列名解析时会被版本差异坑到。

还有一层隐藏问题:LIKE里的下划线是通配符。tmp_%中的_会匹配任意一个字符,所以必须写成tmp\_%。第一次用这个方案的人,十个里有八个会漏掉反斜杠。用来临时确认几张小表可以,但要把它做成一个长期复用的删除工具,清单不可审计这一点就不合格。v1.0 不采用这条路。

2.2 information_schema.tables:v1.0 的清单来源

information_schema.tables是 MySQL 元数据的统一出口,每张表的表名、类型、引擎、行数、大小都在里面,而且对普通账号可读,不需要额外授权。v1.0 的清单查询固定成下面这个结构:

SELECT TABLE_NAME, TABLE_TYPE, ENGINE FROM information_schema.tables WHERE TABLE_SCHEMA = 'my_app' AND TABLE_TYPE = 'BASE TABLE' AND TABLE_NAME LIKE 'tmp\_%' ORDER BY TABLE_NAME;

TABLE_TYPE = 'BASE TABLE'把视图和临时表排除在删除范围外,这是整个查询里最关键的过滤条件。ENGINE字段用来评估表类型,遇到外键依赖时,InnoDB 和 MyISAM 的处理策略完全不一样。ORDER BY TABLE_NAME保证每次执行拿到的清单顺序一致,脚本跑一半中断后,重跑时能稳定跳过已删除的表。

相比SHOW TABLES,这条路的最大价值是清单可过滤、可统计、可 JOIN。比如想统计匹配到多少张表,直接SELECT COUNT(*);想知道哪些表被外键引用,可以 JOINKEY_COLUMN_USAGE。同样是拿前缀匹配表名,这套查询的列名和语义跨版本稳定,适合写进工具里长期使用。

2.3 存储过程:服务器上没有 Python 时的备选方案

如果目标机器上装不了 Python 和客户端库,也可以把删除逻辑直接写进数据库,用存储过程跑。常见做法是游标遍历information_schema.tables,拼出一条 DROP 语句再PREPARE执行:

DELIMITER $$ CREATE PROCEDURE drop_by_prefix(IN db_name VARCHAR(64), IN prefix VARCHAR(64)) BEGIN DECLARE done INT DEFAULT 0; DECLARE tbl VARCHAR(64); DECLARE cur CURSOR FOR SELECT TABLE_NAME FROM information_schema.tables WHERE TABLE_SCHEMA = db_name AND TABLE_NAME LIKE CONCAT(prefix, '%'); DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1; SET @sql = ''; OPEN cur; read_loop: LOOP FETCH cur INTO tbl; IF done THEN LEAVE read_loop; END IF; SET @sql = CONCAT(@sql, '`', tbl, '`,'); END LOOP; CLOSE cur; IF LENGTH(@sql) > 0 THEN SET @sql = CONCAT('DROP TABLE IF EXISTS ', LEFT(@sql, LENGTH(@sql) - 1)); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; END IF; END$$ DELIMITER ;

这段过程能跑,但我只在「服务器没有外部环境」的场景下用它。它的短板很明显:清单没有落盘,执行前看不到到底删哪些表;白名单、批量大小、跳过逻辑全部写死在过程里,想加一个--skip参数就得改存储过程本身;出错时只有一条错误日志,想定位是哪张表触发的外键约束,得自己拼 SQL 去查。

结论很明确:临时清理用 2.2 的查询手工确认足够;想做一个交付给团队复用的工具,外部脚本在可观测性和可控性上远胜存储过程。v1.0 选外部脚本路线。

提示:无论走哪条路线,删表前先确认账号有没有 DROP 权限。information_schema对任何账号可读,能查出清单不代表能删表。

3. 落地 v1.0 脚本:连接配置、前缀匹配、分批 DROP 与演练确认

v1.0 的定位是「可复现、可审计」的小工具:一个 Python 脚本,零配置文件,依赖只有一个pymysql。处理范围限定在单库单前缀,不做正则、不做多库模糊匹配。把边界写死在脚本里,比把灵活性留给使用者更安全。下面的实现按「参数解析 → 查清单 → 分批执行 → 演练确认」四段拆开,每一段都是完整可跑的代码。

3.1 参数设计:把密码交给环境变量,把选择交给命令行

先写参数解析。密码不放在命令行参数里,是因为/proc目录会记录完整命令行,密码容易泄漏给同机其他进程。统一从环境变量MYSQL_PWD读取:

# drop_tables_by_prefix.py import argparse import os import logging import pymysql logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") logger = logging.getLogger("drop_by_prefix") def parse_args(): parser = argparse.ArgumentParser(description="批量删除 MySQL 同前缀数据表 v1.0") parser.add_argument("--host", default="127.0.0.1", help="MySQL 地址") parser.add_argument("--port", type=int, default=3306, help="MySQL 端口") parser.add_argument("--user", required=True, help="数据库账号,需有 DROP 权限") parser.add_argument("--database", required=True, help="目标库名,例如 my_app") parser.add_argument("--prefix", required=True, help="表前缀,例如 tmp_") parser.add_argument("--skip", default="", help="精确跳过的表名,逗号分隔") parser.add_argument("--batch-size", type=int, default=50, help="每批删除的表数量") parser.add_argument("--dry-run", action="store_true", help="演练模式,不执行 DROP") parser.add_argument("--yes", action="store_true", help="跳过交互确认,生产环境慎用") args = parser.parse_args() args.password = os.environ.get("MYSQL_PWD", "") if not args.password: parser.error("请通过环境变量 MYSQL_PWD 传入密码,避免命令行泄漏") return args

这个参数表里最重要的三个开关是--dry-run、--yes和--batch-size。--dry-run保证首次执行只出清单不动数据;--yes是给自动化任务准备的,手动操作时不建议加;--batch-size控制单条 DROP 语句里包含的表数量,后面第 4 章会细讲。各参数含义整理如下:

参数默认值说明
--host127.0.0.1MySQL 地址,生产环境建议填内网地址
--port3306MySQL 端口
--user必填账号需对目标库有 DROP 权限
--database必填目标库名,脚本只允许传一个库
--prefix必填表前缀,含下划线时脚本会自动转义
--skip空精确跳过的表名,逗号分隔
--batch-size50每批删除表数量,生产建议调低
--dry-runFalse演练模式,只生成 SQL 不执行
--yesFalse跳过交互确认,适合定时任务

3.2 从 information_schema 取清单:过滤视图、转义前缀

清单查询是 v1.0 的核心,直接查information_schema.tables,并在 SQL 里固定TABLE_TYPE = 'BASE TABLE':

def list_tables(conn, database, prefix, skip_tables): skip_set = set(skip_tables.split(",")) if skip_tables else set() sql = """ SELECT TABLE_NAME, TABLE_TYPE, ENGINE FROM information_schema.tables WHERE TABLE_SCHEMA = %s AND TABLE_TYPE = 'BASE TABLE' AND TABLE_NAME LIKE %s ORDER BY TABLE_NAME """ # 先转义反斜杠,再转义 LIKE 通配符 _ 和 % escaped = prefix.replace("\\", "\\\\").replace("_", "\\_").replace("%", "\\%") pattern = escaped + "%" with conn.cursor() as cursor: cursor.execute(sql, (database, pattern)) rows = cursor.fetchall() kept = [r for r in rows if r[0] not in skip_set] logger.info("匹配 %s 张表,跳过 %s 张,保留 %s 张", len(rows), len(rows) - len(kept), len(kept)) return kept

转义顺序有讲究:先转义反斜杠本身,再转义下划线和百分号,否则前缀里原有的反斜杠会把后面的转义序列搞乱。LIKE模式最终变成tmp\_%,在 MySQL 默认转义规则下,这个模式严格匹配「以 tmp_ 开头」的表名。skip_set在这里做一次精确过滤,比先查出全部表再在循环里判断更清晰,日志里也能直接看到「匹配、跳过、保留」三个数字。

3.3 分批 DROP:DDL 不走事务,失败后靠重查清单续跑

拿到清单后进入执行层。每条 DROP 语句用逗号连接多张表,一次提交一批。这里有一个 MySQL 的底层事实要记住:DDL 语句隐式提交,DROP TABLE一旦执行成功就立即生效,rollback()对它没有任何意义。所以异常处理里不用回滚,而是中断当前批次,重查清单后继续:

def drop_in_batches(conn, tables, batch_size): total = len(tables) for i in range(0, total, batch_size): batch = tables[i:i + batch_size] # 反引号包住表名,防止表名含特殊字符时 SQL 语法错误 names = ", ".join("`%s`" % t[0] for t in batch) sql = "DROP TABLE IF EXISTS %s" % names try: with conn.cursor() as cursor: cursor.execute(sql) logger.info("已删除 %s/%s 张表", min(i + batch_size, total), total) except Exception as e: logger.error("本批删除失败: %s", e) break

IF EXISTS不是可有可无的保险:脚本执行到一半断网时,上一批可能已经删掉一部分表,重跑时这些表不再存在于information_schema,但DROP TABLE IF EXISTS会让语句安静地跳过它们。反引号同样重要,表名里出现-或者中文时,不加反引号直接拼接会被 MySQL 当成表达式。失败中断后最稳妥的重试方式不是原地重试,而是重新调用list_tables拿最新清单,已经删掉的表自然不在里面,自动续跑。

3.4 演练模式与交互确认:不确认就不碰数据

最后把主流程串起来。--dry-run模式下不执行任何 DROP,而是把完整的删除计划写入drop_plan.sql,这份文件既是演练证据,也是审计留痕:

def write_drop_plan(tables, batch_size, path): with open(path, "w") as f: for i in range(0, len(tables), batch_size): batch = tables[i:i + batch_size] names = ", ".join("`%s`" % t[0] for t in batch) f.write("DROP TABLE IF EXISTS %s;\n" % names) logger.info("演练完成,SQL 已写入 %s", path) def main(): args = parse_args() conn = pymysql.connect( host=args.host, port=args.port, user=args.user, password=args.password, database=args.database, charset="utf8mb4" ) try: tables = list_tables(conn, args.database, args.prefix, args.skip) if not tables: logger.info("没有匹配的表,结束") return for t in tables[:10]: logger.info("候选: %s | %s | %s", t[0], t[1], t[2]) if len(tables) > 10: logger.info("... 共 %s 张,仅预览前 10 张", len(tables)) if args.dry_run: write_drop_plan(tables, args.batch_size, "drop_plan.sql") return if not args.yes: answer = input("确认删除以上 %s 张表? 输入 yes 继续: " % len(tables)) if answer != "yes": logger.info("已取消") return drop_in_batches(conn, tables, args.batch_size) finally: conn.close() if __name__ == "__main__": main()

实际使用分两步走。第一次先演练:

export MYSQL_PWD='填入密码' python drop_tables_by_prefix.py --host 127.0.0.1 --port 3306 \ --user app_cleaner --database my_app --prefix 'tmp_' --dry-run

确认drop_plan.sql里的每一行都符合预期后,再执行真实删除:

python drop_tables_by_prefix.py --host 127.0.0.1 --port 3306 \ --user app_cleaner --database my_app --prefix 'tmp_' --yes

这里不建议直接跳过演练。drop_plan.sql不只是一份 SQL 文件,它是你删表前最后一次人工复核的机会,也是对「到底删了哪些表」的硬证据。脚本本身再稳,也替代不了人眼扫一遍清单。

4. 批量删表的三个必调参数:LIKE 转义、白名单上限、分批大小

脚本能跑通只是开始,真正决定删表过程安不安全的是三个参数的取值:前缀怎么匹配、哪些表必须跳过、每批删多少。v1.0 把这三点都放在命令行参数层,方便每个环境按自己的情况调整。这三个参数调不好,前面所有代码都等于给事故现场递刀。

4.1 前缀匹配的边界:下划线是通配符,不是表名字符

前缀匹配是最容易翻车的一环。假设要清理的表前缀是log_2024_,直接拼LIKE 'log_2024_%'时,MySQL 会把两个下划线当成「任意单字符」通配符,匹配范围瞬间变成logX2024X...。原本只想清日志表,结果把logA2024B_user这类命名规则沾边的表也捞了进来。

v1.0 的处理方式是在查询前对前缀做转义,把_替换成\_,把%替换成\%。这样log_2024_就变成严格的前缀字面量,只匹配真正以log_2024_开头的表。如果目标环境开启了NO_BACKSLASH_ESCAPESSQL 模式,反斜杠转义会失效,这时候可以改用更稳妥的等价写法:

SELECT TABLE_NAME, TABLE_TYPE, ENGINE FROM information_schema.tables WHERE TABLE_SCHEMA = 'my_app' AND TABLE_TYPE = 'BASE TABLE' AND LEFT(TABLE_NAME, LENGTH('log_2024_')) = 'log_2024_' ORDER BY TABLE_NAME;

LEFT()比较虽然不能走索引,但information_schema查询本身开销很低,几十万张表的元数据全扫一遍也就毫秒级,安全优先于性能。调参时的核心原则是:命令行传入原始前缀,脚本负责转义,使用者永远不要自己手动加反斜杠——手动加一次就会有人忘记第二次。

4.2 跳过保护表:白名单与数量兜底

有些表虽然顶着临时前缀,但不能删。常见的是定时任务的状态表、给报表用的汇总表、临时上线的补数表。v1.0 里用--skip精确跳过:

python drop_tables_by_prefix.py --database my_app \ --prefix 'tmp_' \ --skip 'tmp_keep_this,tmp_keep_that' --dry-run

--skip做的是全名精确匹配,防止tmp_user_count这种表被误伤。光有白名单还不够,还要加一道数量兜底。当前缀写得过宽、匹配到的表远超预期时,应该让脚本直接拒绝执行。给 main 函数加一个上限判断:

parser.add_argument("--max-count", type=int, default=0, help="匹配表数超过该值直接拒绝执行,0 表示不限制")

在拿到清单后立刻校验:

if args.max_count and len(tables) > args.max_count: logger.error("匹配表数 %s 超过上限 %s,终止执行", len(tables), args.max_count) return

这个参数的价值在于:它把「人肉核对清单」的最后一道防线自动化了。某次我把前缀看错一位,tmp_写成t_,如果当时有--max-count 300兜底,脚本会在执行前停下来,而不是直接对五百多张表发起 DROP。

4.3 分批大小与删除顺序:稳定比快更重要

DROP TABLE t1, t2, t3支持一条语句删多张表,但单条语句太长会碰到max_allowed_packet限制。更重要的是,MySQL 的 DDL 隐式提交,一批语句里只要有一张表处于元数据锁等待,整批都会卡住,而且无法回滚。所以分批大小不是越大越好,--batch-size默认 50,生产环境我一般调到 30。

删除顺序同样有讲究:先删小表,再删大表。小表意味着更少的磁盘页释放和更短的锁持有时间;即使中途失败,中断点之前的表已经删完,重跑成本最低。如果想按表大小排序,在list_tables里把DATA_LENGTH + INDEX_LENGTH也查出来,ORDER BY (DATA_LENGTH + INDEX_LENGTH) ASC即可。对清理临时表来说,按名字顺序删不会出错,但按大小顺序删会让整个过程更平滑。

5. 批量删表避坑:5 个现场让我不敢再一把梭

代码写得再小心,生产环境也会给出意想不到的回应。这 5 个现场是我在清理临时表时踩过的,按「现象→原因→解决」写下来,希望对正在做同类清理的人有用。

5.1 DROP 执行到一半卡住:元数据锁等待与长事务

现象:脚本停在第 30 张表,日志不再输出,SHOW PROCESSLIST里看到目标线程的状态是Waiting for table metadata lock。

原因:某个定时任务或慢查询还持着目标表的元数据锁。DROP TABLE需要拿排他元数据锁,拿不到就一直在等。这类锁通常不会自动超时,表现就是脚本悬挂在那里,看起来像死机。

解决:先查information_schema.innodb_trx定位长事务:

SELECT trx_id, trx_state, trx_started, trx_mysql_thread_id, trx_query FROM information_schema.innodb_trx ORDER BY trx_started;

找到持有锁的会话后,确认是空闲事务再KILL对应线程。之后重跑脚本即可。v1.0 每次执行都会重新查information_schema拿清单,已删除的表不会再次进入列表,续跑是天然安全的,不需要额外做断点记录。

5.2 视图混进删除清单:没过滤 TABLE_TYPE 的后果

现象:清单里多出tmp_v_xxx这类视图,轻则 DROP 报错打断整批,重则把依赖该视图的上游查询全部搞挂。

原因:第一版图省事用了SHOW TABLES LIKE,它不会区分表、视图、临时表,凡是名字匹配前缀的全部返回。

解决:统一走information_schema.tables,并且强制带上TABLE_TYPE = 'BASE TABLE'条件。v1.0 的清单查询里这个条件不是可选项,是写死的。另外,预览清单时把TABLE_TYPE一并打印出来,人工复核时多看一眼,基本能杜绝这类事故。

5.3 有外键关联的表批量 DROP:报错还是挂起要看引用方向

现象:批量 DROP 时遇到被其他表引用的父表,报ER_ROW_IS_REFERENCED_2。表现可能是整批停在原地,也可能是删到一半突然报错。

原因:目标表被其他表的外键引用,MySQL 不允许直接删父表。临时把FOREIGN_KEY_CHECKS关掉再 DROP 是一个流传很广的写法,但关掉外键检查删父表会让子表里的引用变成孤儿数据,后续业务写入直接失败。

解决:删表前先查一遍外键依赖,把引用关系打印出来:

SELECT TABLE_NAME, COLUMN_NAME, REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME FROM information_schema.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA = 'my_app' AND REFERENCED_TABLE_NAME IS NOT NULL AND REFERENCED_TABLE_NAME LIKE 'tmp\_%';

拿到报告后,先删子表再删父表。v1.0 的规范做法是把外键报告也落盘,和drop_plan.sql放一起,删完留档。

5.4 删完才想起备份:ROW 格式 binlog 也帮不上 DDL 的忙

现象:误删了一张还带数据的表,想用 binlog 做 Flashback,结果发现 binlog 里只有一条DROP TABLE记录,这张表之前的所有数据变更都无从恢复。

原因:DROP TABLE是 DDL,binlog 里只记录一条 statement;ROW 格式的 Flashback 工具只对 DML 有效,遇到 DDL 直接断链。

解决:删表前先把表结构落盘。mysqldump --no-data或者最简单的SHOW CREATE TABLE都可以,至少能把表结构和字符集存下来;数据层面,v1.0 执行前的drop_plan.sql也是一种轻量留痕。真正的重要业务表不应该进批量删除清单,它应该出现在--skip白名单里。

5.5 权限不足的隐藏现场:SELECT 能查清单,DROP 却报错

现象:脚本在查information_schema时一切正常,清单也打印出来了,执行 DROP 时却报DROP command denied to user 'app_cleaner'@'%'。

原因:账号对目标库只有SELECT、CREATE权限,没有DROP。information_schema对任何账号可读,能查出清单不代表有权限删。

解决:执行前先跑SHOW GRANTS FOR 'app_cleaner'@'%'核对权限;给清理任务单独建一个账号,只授予目标库的DROP、SELECT权限,不要图省事直接把 root 配置进脚本。权限收紧虽然多一道审批,但能避免清理账号被用来删掉整个生产库。

6. 让 v1.0 不翻车的三个收尾动作:清单快照、干跑 DDL、删后核对

脚本能跑、参数调完,最后还有三个动作能显著降低生产事故概率。它们几乎不花时间,但能在误删发生后给你一张后悔药。

6.1 清单快照:删前落盘、删后 diff

执行之前,先让 v1.0 以--dry-run输出一份before.txt;删除完成后,再跑一次--dry-run输出after.txt。两份文件 diff 一下,对比结果一目了然:

python drop_tables_by_prefix.py --database my_app \ --prefix 'tmp_' --dry-run > before.txt python drop_tables_by_prefix.py --database my_app \ --prefix 'tmp_' --yes python drop_tables_by_prefix.py --database my_app \ --prefix 'tmp_' --dry-run > after.txt diff before.txt after.txt

before.txt就是审计证据,它记录了这次清理到底删了哪些表;after.txt如果还有内容,说明删除不完整,需要人工介入。diff 一行都看不到,才算是干净收尾。

6.2 干跑 DDL:在临时库验证 drop_plan.sql

把drop_plan.sql拿到临时库执行一遍,能提前发现两类问题:表名里包含特殊字符导致反引号拼接不完整,以及同一条批量 DROP 语句里混入了被外键引用的表。临时库建议用相同大版本的 MySQL,避免语法差异造成的误判。干跑通过不代表生产环境一定没问题,但它能过滤掉大部分低级错误,把生产执行的风险收敛到元数据锁和权限这两个维度。

6.3 删后核对:看余量,也看释放空间

删完最后再做一次核对,确认前缀匹配数量归零:

SELECT COUNT(*) AS remaining FROM information_schema.tables WHERE TABLE_SCHEMA = 'my_app' AND TABLE_TYPE = 'BASE TABLE' AND TABLE_NAME LIKE 'tmp\_%';

如果还想记录这次清理的成果,删除前先统计一次这些表占用的空间,删完再查一次做对比:

SELECT IFNULL(SUM(DATA_LENGTH + INDEX_LENGTH), 0) AS reclaim_bytes FROM information_schema.tables WHERE TABLE_SCHEMA = 'my_app' AND TABLE_NAME LIKE 'tmp\_%';

我现在删任何一组表,都先把 dry-run 的drop_plan.sql留一份,再执行,删完 diff 一次。之前有次跳过快照直接--yes,删完之后才发现有一张tmp_开头的表是定时任务的依赖表,恢复结构花了整个下午。从那天起,「先落盘、再执行、后核对」成了固定动作。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询