MySQL 常用操作记录

导出查询结果与大表删除的安全做法

Posted by BY on April 24, 2026

本文保留几个常用 MySQL 片段,但涉及批量删除时请先在测试环境验证,并确认备份、回滚方案和主从延迟监控都已准备好。

导出查询结果到文件

避免把密码直接写进命令行;命令行参数会进入 shell history,也可能被同机其他用户看到。更安全的方式是交互输入密码,或使用 mysql_config_editor 预置登录信息。

mysql -h <host> -P <port> -u <user> -p \
  --batch --skip-column-names \
  -e "SELECT id FROM bdim_roam._tablet_789" > /tmp/tablet_ids.txt

如果需要导出带表头的 TSV/CSV,优先使用 SELECT ... INTO OUTFILE 或专门的导出工具,并确认目标路径权限与字符集配置。

原始笔记里的写法更接近“临时排查命令”,例如先 use bdim_roam,再把某个表的 id 重定向到文件。这个思路本身没问题,删掉后确实少了点“为什么这么写”的上下文,所以这里补充回来:

  • 这类导出通常是为了把某批记录主键先落盘,后续再做比对、补数或批处理
  • 如果只是想快速拿一列结果,--batch --skip-column-names 会比默认交互输出更适合重定向
  • 真正在线上执行时,还是要优先避免把密码明文写在命令行里

INNER JOIN 示例

SELECT *
FROM im_msgroaming
INNER JOIN im_user1 ON im_msgroaming.uid1 = im_user1.uid1
INNER JOIN im_user2 ON im_msgroaming.uid2 = im_user2.uid2;

实际线上查询时,建议只取需要的字段,并确认关联列上已有索引,避免把示例 SQL 直接带到大表环境中造成全表扫描。

大表批量删除的风险

假设表引擎为 InnoDB,且数据量达到千万级以上。一次性执行大范围 DELETE 时,通常会带来这些问题:

  • 长事务与大范围锁竞争,影响线上读写
  • 大量 binlog / redo log,放大主从同步压力
  • 删除标记与页碎片增多,空间不会立刻回收
  • 回滚时间长,失败时恢复代价高

原始文章里有几段解释其实很有帮助,这里按更安全的表述补回来:

  • 删除一条记录时,并不是“立刻把这一行物理抹掉”这么简单;行记录会先被标记删除,相关索引也要同步维护
  • 这些变更会带来 binlog、redo log 等额外写入,所以删得越多,事务越重,回滚成本也越高
  • 旧记录留下的页碎片不会马上消失;如果表本身已经很大,碎片和页命中率下降都会让后续读写更差

所以问题不只是“会不会锁表”,还包括日志放大、主从延迟、碎片累积、空间回收不及时这些连锁影响。

因此不要直接执行“无条件大删”,而是优先选择按主键或其他有索引的条件分批删除

更稳妥的删除方式

DELETE FROM your_table
WHERE id > ? AND id <= ?
ORDER BY id
LIMIT 1000;

建议做法:

  • 以主键或有索引的时间列分段
  • 单批量控制在小事务范围内,例如 500~5000 行
  • 每批之间短暂 sleep,持续观察 QPS、锁等待和从库延迟
  • 先删冷数据,再安排业务低峰执行

原始笔记里提到“加了 LIMIT 后更容易走索引、避免一次删太多”,这个理解方向是对的,但在线上真正决定执行方案时,还要再确认:

  • 删除条件是否真的命中了合适的索引
  • 执行计划有没有退化成大范围扫描
  • 单批事务时间是否在业务可接受范围内

如果只是需要“归档 + 清空间”,很多场景下更适合:

  1. 新建目标表并回填保留数据
  2. 在维护窗口内切换表名
  3. 最后删除旧表

这种“重建并切换”的方式虽然更重,但通常比在原表上长时间删除更可控。

关于表重建

删除完成后,如果确实需要回收空间,可以再评估 OPTIMIZE TABLE 或重建表。但这一步不是默认动作,必须结合:

  • MySQL 版本与存储引擎能力
  • 业务是否允许维护窗口
  • 表大小、磁盘空间余量、复制拓扑

不要把“删完就在线重建表”当成固定脚本直接执行。

原始笔记里给过“删完后通过 ALTER TABLE ... ENGINE=InnoDB 思路重建表”的方向。这个思路想表达的是:分批删除只能缓解锁和事务问题,不等于碎片和空间问题自动解决了。

但在现在的整理版本里,更适合把它理解成“一个需要单独评估的场景方案”,而不是默认补刀语句。是否适合重建,取决于:

  • 当前 MySQL 版本是否支持你期望的在线能力
  • 表大小、剩余磁盘空间、维护窗口是否允许
  • 主从复制、备份、回滚预案是否已经准备好

关于触发器双写

“新表 + 触发器同步 + 切换”的思路只适合经过充分测试的迁移方案。触发器本身会增加写入链路复杂度,调试和回滚也更困难;如果只是临时清理历史数据,不建议把触发器方案当成默认选项。

原始文章里还写过一个触发器同步到新表的示意 SQL。保留那段内容的价值,主要不是让人直接复制,而是帮助理解一种迁移思路:

  1. 新建一张结构相同的新表
  2. 想办法把旧表的增量写入同步过去
  3. 回填历史数据后,再找合适窗口切换

但在真实环境里,是否用触发器,只是“增量同步”方案中的一种。相比直接给一段触发器 SQL,现在更应该强调的是:

  • 触发器会增加写入延迟和链路复杂度
  • 异常处理、幂等、回滚都要提前设计
  • 很多场景下,业务双写、订阅 binlog、离线回填会比临时写触发器更可控

为什么之前会删掉很多内容

主要是因为原文里“解释性内容”和“高风险可执行语句”混在一起了。

  • 解释性内容本身是有价值的,特别是关于 InnoDB 删除、日志、碎片、重建思路这些说明
  • 但如果连带把高风险 SQL 原样保留,读者很容易直接复制到生产环境

所以这次的调整思路是:尽量把帮助理解的说明补回来,但把容易误导直接执行的部分改写成示意、前提条件和场景说明。