本文保留几个常用 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 后更容易走索引、避免一次删太多”,这个理解方向是对的,但在线上真正决定执行方案时,还要再确认:
- 删除条件是否真的命中了合适的索引
- 执行计划有没有退化成大范围扫描
- 单批事务时间是否在业务可接受范围内
如果只是需要“归档 + 清空间”,很多场景下更适合:
- 新建目标表并回填保留数据
- 在维护窗口内切换表名
- 最后删除旧表
这种“重建并切换”的方式虽然更重,但通常比在原表上长时间删除更可控。
关于表重建
删除完成后,如果确实需要回收空间,可以再评估 OPTIMIZE TABLE 或重建表。但这一步不是默认动作,必须结合:
- MySQL 版本与存储引擎能力
- 业务是否允许维护窗口
- 表大小、磁盘空间余量、复制拓扑
不要把“删完就在线重建表”当成固定脚本直接执行。
原始笔记里给过“删完后通过 ALTER TABLE ... ENGINE=InnoDB 思路重建表”的方向。这个思路想表达的是:分批删除只能缓解锁和事务问题,不等于碎片和空间问题自动解决了。
但在现在的整理版本里,更适合把它理解成“一个需要单独评估的场景方案”,而不是默认补刀语句。是否适合重建,取决于:
- 当前 MySQL 版本是否支持你期望的在线能力
- 表大小、剩余磁盘空间、维护窗口是否允许
- 主从复制、备份、回滚预案是否已经准备好
关于触发器双写
“新表 + 触发器同步 + 切换”的思路只适合经过充分测试的迁移方案。触发器本身会增加写入链路复杂度,调试和回滚也更困难;如果只是临时清理历史数据,不建议把触发器方案当成默认选项。
原始文章里还写过一个触发器同步到新表的示意 SQL。保留那段内容的价值,主要不是让人直接复制,而是帮助理解一种迁移思路:
- 新建一张结构相同的新表
- 想办法把旧表的增量写入同步过去
- 回填历史数据后,再找合适窗口切换
但在真实环境里,是否用触发器,只是“增量同步”方案中的一种。相比直接给一段触发器 SQL,现在更应该强调的是:
- 触发器会增加写入延迟和链路复杂度
- 异常处理、幂等、回滚都要提前设计
- 很多场景下,业务双写、订阅 binlog、离线回填会比临时写触发器更可控
为什么之前会删掉很多内容
主要是因为原文里“解释性内容”和“高风险可执行语句”混在一起了。
- 解释性内容本身是有价值的,特别是关于 InnoDB 删除、日志、碎片、重建思路这些说明
- 但如果连带把高风险 SQL 原样保留,读者很容易直接复制到生产环境
所以这次的调整思路是:尽量把帮助理解的说明补回来,但把容易误导直接执行的部分改写成示意、前提条件和场景说明。