面试官推过来一张纸,上面只有一行字:
DELETE FROM operation_log WHERE create_time < '2024-01-01';
他敲了敲桌子:"你本想清掉三个月前的日志,SQL都写好了,复制粘贴进终端时,手一抖,WHERE那一行没粘进去。你直接回车了。
实际执行的,是这行:
DELETE FROM operation_log;
表里有10亿条数据,主从同步,没有延迟从库。现在,拿什么救命?"
我手心冒汗。这是字节二面,不是背八股,是现场解题。
一、第一回合:现场评估——先止血,再救人
面试官:第一步做什么?
我:先停应用写入,把主库的写流量切走或限流,防止新数据覆盖旧数据的binlog位置。然后确认三件事:
表结构:有没有主键?有没有外键约束?有没有触发器?(这些会影响闪回后数据一致性)
binlog状态:格式是
ROW还是STATEMENT?expire_logs_days还剩几天?备份窗口:最近一次全量备份是昨晚几点?能不能接受丢数据?
面试官:如果binlog是STATEMENT格式呢?
我:那这道题直接判死刑。STATEMENT只记录SQL文本,那条DELETE在binlog里就是一行字符串,没有行级镜像,任何闪回工具都无能为力。只能拿昨晚的全备,叠加上备份点之后的binlog做增量恢复,业务至少丢几小时数据,而且恢复期间必须停服。
面试官:你们线上用ROW还是STATEMENT?
我:必须是ROW。STATEMENT省的那点磁盘空间,抵不上一次误删的代价。而且ROW格式下,binlog会记录每行删除前的完整镜像,这是后面所有操作的前提。
二、第二回合:binlog闪回——和时间赛跑
面试官:确认是ROW格式了,现场写恢复命令。
我:用binlog2sql或MyFlash。以binlog2sql为例:
# 1. 先定位误删的binlog文件和位置mysqlbinlog --start-datetime="2024-04-01 14:00:00"\--stop-datetime="2024-04-01 14:05:00"\mysql-bin.000045 | grep -i "DELETE FROM operation_log"# 2. 解析生成反向SQL(DELETE转INSERT)python binlog2sql.py -h127.0.0.1 -P3306 -uadmin -p'xxx' \-d operation_db -t operation_log \--start-file='mysql-bin.000045' \--start-pos=123456\--stop-pos=234567\-B > rollback.sql
-B参数就是flashback模式,把DELETE事件反向生成INSERT。
面试官:10亿行数据,生成反向SQL要多久?直接执行rollback.sql吗?
我:不能直接执行。10亿行的INSERT文件可能有几百GB,直接灌回去会打爆主库IO,触发从库延迟,甚至把业务拖垮。
正确做法是:
分段处理:按主键范围切分,比如每批100万行,用
LIMIT或WHERE id BETWEEN分批回插。先导入临时表:
CREATE TABLE operation_log_recovery LIKE operation_log
,把闪回数据先写进临时表,校验行数、抽样比对关键字段,确认无误后再做RENAME TABLEINSERT ... SELECT合并。低峰期操作:如果业务能接受,凌晨执行;如果不能停服,临时表数据补完后,用
pt-table-sync或主键对比的方式增量补齐操作期间的新增数据。
面试官:如果binlog刚好第8天被清理了呢?
我:这就看公司的expire_logs_days配置了。字节这种体量,binlog一般会保留7-15天,且会异地归档到对象存储(比如S3或内部HDFS)。如果本地被清理但归档还在,下载回来解析就行;如果连归档都没有,那只能回退到全量备份+增量binlog的恢复路径,丢数据是肯定的。
面试官:闪回期间,业务要不要停服?
我:理想情况不停。但如果原表有自增主键或唯一索引,反向插入时可能和新写入的数据冲突。这时候有几种策略:
临时表恢复后,把冲突区间的自增值手动跳过;
或者短暂停写(分钟级),完成数据合并后再开放;
最稳妥的是:先切到从库读,主库只留恢复操作。
面试官:你们公司还有PG和Redis,假设同样的误操作发生在PostgreSQL和Redis,分别怎么救?
我:
PostgreSQL:PG没有binlog,靠WAL(Write-Ahead Log)+ 归档做PITR(Point-in-Time Recovery)。需要提前有pg_basebackup的全量基准备份,再加上连续的归档WAL。误删后创建recovery.conf,设置recovery_target_time到误操作前1秒,启动实例重放WAL。但PG的PITR是整库级的,不像MySQL能单表闪回。如果只想恢复一张表,得先恢复到临时实例,再把那张表pg_dump出来灌回去。
Redis:如果开了AOF(appendonly=yes),AOF文件是纯文本指令序列。找到那条DEL或FLUSHALL命令,直接编辑AOF文件删掉它,重启Redis重新加载即可。如果开了RDB,只能用最近一次快照恢复,快照之后的数据全丢。Redis 4.0+的混合持久化(RDB全量+AOF增量)也是优先加载AOF,思路一样。
面试官:PG和Redis的恢复,哪个更麻烦?
我:PG更麻烦。Redis的AOF是文本,肉眼能改;MySQL的binlog有成熟工具;PG的WAL是二进制物理日志,必须走整库PITR,周期长、空间大、对运维要求高。
四、第四回合:没有备份——从"技术题"变成"送命题"
面试官:假设你们公司很穷,没有全备,binlog也没归档,还有救吗?
我:技术上还有最后一根稻草——磁盘级数据恢复。InnoDB的.ibd文件即使被DROP,只要磁盘扇区没被新数据覆盖,可以找专业公司扫描底层、按页解析IBD结构。但按MB收费,10亿行的表恢复可能要几万到十几万,且不保证完整。
这时候问题已经从"技术问题"变成了"商务问题":是花大钱抢救,还是接受业务停摆?
面试官:结论?
我:备份不是可选项,是必选项。没有备份的恢复叫"赌博",而且十赌九输。
五、第五回合:预防措施——真正厉害的工程师,不让事故发生
面试官:如果让你设计一套机制,保证这类事故不发生,你怎么做?
我:三层防线:
1. 事前拦截
生产账号只读,DML走工单系统(Archery/Yearning),SQL必须双人复核。
危险命令(
DROP、TRUNCATE、DELETE无WHERE)直接拦截,正则匹配到就拒绝执行。开发环境用
sql_safe_updates强制要求UPDATE/DELETE必须带WHERE和LIMIT。
2. 事中保护
操作前
EXPLAIN确认影响行数;大表操作分批+事务包裹。核心库必须配延迟从库(
MASTER_DELAY = 3600),这是最后的后悔药。变更窗口期放在低峰期,操作前先做
CREATE TABLE ... SELECT备份当前表。
3. 事后兜底
每天全备 + binlog/WAL归档,且必须做恢复演练。很多公司备份做了三年,真用时发现备份文件损坏或依赖的binlog已被清理,等于没备。
面试官合上笔记本,说了一句:
"技术救急是下限,规范流程是上限。真正厉害的工程师,不是删了数据能恢复的人,而是让删错数据这件事根本不容易发生的人。"
我深吸一口气。这题,我活下来了。
![]()
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.