64. 百万级别以上的数据如何删除?
答:
删除百万级、千万级数据时,最忌讳的做法就是直接一条:
DELETE FROM big_table WHERE ...尤其是在生产环境中,这样做可能带来很大风险:
- 长事务
- 大量行锁 / 间隙锁
- 大量 undo / redo / binlog
- 主从延迟
- CPU / IO 飙高
- 表膨胀
- 影响线上业务
所以大数据量删除一定要采用“分批、小步、可控”的策略。
1)为什么大批量删除很危险?
(1)事务太大
一条大 DELETE 会产生很大的事务,导致:
- undo log 暴涨
- redo log 压力大
- binlog 很大
- 回滚代价极高
(2)锁持有时间长
如果删除过程很久,锁会长时间不释放,影响其他业务读写。
(3)主从延迟
主库执行一条超级大的删除语句,从库回放也会很慢,容易出现严重延迟。
(4)表空间不会立刻缩小
即使删掉了很多数据,InnoDB 文件空间通常也不会马上归还操作系统,只是标记为可复用。
2)正确思路:分批删除
最常见做法是:
按主键或索引范围,分批次删除,每次删少量。
例如每次删 1000 条:
DELETE FROM ordersWHERE id >= 1 AND id < 1001;然后循环往下删。
或者:
DELETE FROM ordersWHERE create_time < '2023-01-01'LIMIT 1000;注意:如果用 LIMIT 删除,最好结合有序主键或索引条件,否则可能效率很差。
3)为什么分批删除更安全?
因为每次删除的数据量小,可以做到:
- 事务短
- 锁持有时间短
- redo / undo / binlog 压力小
- 对线上影响更小
- 可以随时暂停
这是生产环境里最实用的方法。
4)分批删除的一般建议
(1)优先按主键范围删
因为主键通常有序,执行更稳定。
例如:
DELETE FROM ordersWHERE id BETWEEN 100001 AND 101000;(2)每批大小要控制
常见经验值:
- 500
- 1000
- 2000
具体要看:
- 机器性能
- 表大小
- 业务峰值
- 主从同步情况
(3)删除批次之间适当 sleep
例如每删一批暂停几十毫秒 / 几百毫秒,减少对线上冲击。
5)如果删除的是历史数据,更好的方式是什么?
如果是按时间删除大量历史数据,且表结构允许,通常更推荐:
方案一:分区表 + 删除分区
如果表按时间分区,那么删除历史数据时直接:
ALTER TABLE ... DROP PARTITION ...这比逐行 delete 快得多。
方案二:归档后重建表
如果要删的数据特别多,甚至占表的大部分,有时会考虑:
- 建新表
- 只导入要保留的数据
- 替换旧表
这种方式本质上是“保留少量、重建整表”,比删除海量数据更高效。 不过实现复杂,需要评估停机或灰度迁移方案。
6)能不能先删索引再删数据?
这个说法在某些离线场景下成立,但在线业务表一般要非常谨慎。
理论上:
- 删除数据时索引越多,维护成本越高
- 所以先删索引再删数据,速度可能更快
但在线生产环境通常不这么干,因为:
- 删索引本身就有风险
- 删完索引可能影响线上查询
- 重建索引也可能很重
所以生产里更常见的仍然是:
- 分批删除
- 限速执行
- 错峰操作
7)删除后空间会立刻释放吗?
通常不会。 InnoDB 删除记录后,空间一般是:
- 标记为可复用
- 不是立刻归还操作系统
如果想真正缩小表文件,可能要:
OPTIMIZE TABLE- 重建表
ALTER TABLE ... ENGINE=InnoDB
但这些操作通常代价不小,要谨慎。
8)总结
删除百万级数据的正确姿势:
- 不要一条大
DELETE - 优先按主键 / 索引分批删除
- 每批控制数量
- 分批之间适当休眠
- 错峰执行
- 必要时考虑分区表或重建表方案
一句话总结:
大数据量删除的核心原则是:小批量、可中断、低冲击。
65. 百万千万级大表如何添加字段?
答:
给大表加字段,是生产环境里非常敏感的操作。 因为表很大时,直接执行:
ALTER TABLE big_table ADD COLUMN ...可能会带来:
- 长时间锁表
- 业务阻塞
- 主从延迟
- CPU / IO 飙高
- 甚至导致线上故障
所以大表加字段不能只看 SQL 写法,更重要的是变更方式。
1)为什么大表加字段有风险?
不同 MySQL 版本、不同字段类型、不同变更形式,对 ALTER TABLE 的影响差异很大。
大表加字段可能引发的问题有:
(1)需要重建表
某些 DDL 会触发表重建,数据量越大越慢。
(2)元数据锁(MDL)
即使是在线 DDL,也可能在开始和结束阶段持有元数据锁。 如果此时有长事务、慢查询,可能互相阻塞。
(3)主从延迟
DDL 在主库执行后,从库也要执行,可能导致复制延迟。
2)常见方案一:使用 Online DDL
在较新版本 MySQL 中,某些加字段操作可以走在线 DDL,减少锁表影响。
例如某些场景下:
ALTER TABLE user ADD COLUMN remark VARCHAR(255) NULL;可能支持较低影响的方式执行。
但要注意:
- 不是所有 DDL 都能真正“无锁”
- 不同版本、不同操作支持程度不同
- 仍需评估执行时间和 MDL 风险
3)常见方案二:pt-online-schema-change
这是生产环境非常经典的方案。
工具:
pt-online-schema-change
它是 Percona 提供的在线改表工具。
原理大致是:
- 新建一张结构变更后的临时表
- 在旧表上建触发器
- 把旧表数据分批拷贝到新表
- 拷贝期间通过触发器同步增量变更
- 数据追平后,最终原子性
rename切换新旧表
优点
- 对线上影响相对较小
- 适合大表在线改结构
缺点
- 实现复杂
- 会用到触发器
- 需要评估主从复制和业务兼容性
4)常见方案三:先改从库,再主从切换
如果系统有主从架构,可以考虑:
- 先在从库做表结构变更
- 验证从库正常
- 主从切换
- 再处理原主库
这种方式适合:
- 有完整主从切换能力
- 业务能接受切换窗口
- DBA 运维能力较强
5)常见方案四:新建表迁移
对于特别大的表,有时会采用“新表迁移”方案:
- 新建带新字段的表
- 历史数据迁移
- 增量双写
- 校验
- 切换流量
- 下线旧表
这和不停机扩容思路很像,复杂但风险可控。
6)如果字段允许,优先加可空字段
通常加一个:
- 可空字段
- 没有复杂默认值
- 末尾追加字段
相对风险更低。
例如:
ALTER TABLE user ADD COLUMN remark VARCHAR(255) NULL;往往比:
- 非空字段
- 带复杂默认值字段
- 中间插入字段
更容易在线执行。
7)上线前要做什么准备?
(1)先在测试环境演练
模拟真实数据量,估算执行时间。
(2)检查长事务
避免 DDL 被 MDL 锁卡住。
(3)选择低峰时段
减少业务冲击。
(4)关注主从延迟
尤其在线上主从架构中。
(5)有回滚预案
DDL 一旦执行,回滚成本可能很高,所以必须有兜底方案。
8)总结
大表加字段常见可行方案有:
- Online DDL
- pt-online-schema-change
- 先改从库再切换
- 新表迁移双写切换
一句话总结:
大表加字段的关键不是 SQL 本身,而是如何在不阻塞业务的情况下安全变更表结构。
66. MySQL 数据库 CPU 飙升的话,要怎么处理呢?
答:
MySQL CPU 飙升通常说明数据库正在执行高消耗操作。 这类问题排查要分两步:
- 先确认是不是 MySQL 导致
- 再定位到底是哪类 SQL / 哪类操作导致
这是典型线上故障排查题,回答时建议按“排查流程 + 处理手段”来讲。
1)第一步:先确认是不是 mysqld 导致
先在机器上用:
top或者:
htop确认 CPU 高占用的进程是不是 mysqld。
因为 CPU 飙升不一定是 MySQL,也可能是:
- Java 应用
- 代理层
- 系统任务
- 监控进程
如果确认是 mysqld,再继续往下查。
2)第二步:查看当前活跃 SQL
最直接的方法:
SHOW FULL PROCESSLIST;关注:
- 哪些 SQL 执行时间很长
- 哪些线程状态异常
- 是否有大量相同 SQL
- 是否有锁等待
- 是否有 Sending data、Sorting result、Copying to tmp table 等高消耗状态
3)第三步:分析是不是某条慢 SQL 导致
如果发现某条 SQL 很突出,就进一步:
- 把 SQL 拿出来
EXPLAIN- 分析是否走索引
- 是否全表扫描
- 是否排序 / 临时表
- 是否扫了大量数据
CPU 飙高最常见原因之一就是:
- 差 SQL
- 缺索引
- 索引失效
- 大量回表
- 大量排序聚合
4)第四步:看是不是突然连接数暴涨
有时不是单条 SQL 特别慢,而是短时间内大量请求同时打到数据库。
这会导致:
- 大量 session
- 并发解析 / 执行 SQL
- CPU 被打满
此时要看:
- 连接数是否异常增长
- 某个接口是否流量暴涨
- 是否有爬虫 / 攻击 / 重试风暴
- 应用是否连接泄漏
5)第五步:看是否有大排序 / 大聚合 / 临时表
某些查询即使不是全表扫描,也可能因为:
ORDER BYGROUP BYDISTINCT- 大结果集排序
- 临时表操作
导致 CPU 使用很高。
这类 SQL 往往在 EXPLAIN 的 Extra 中能看到:
Using temporaryUsing filesort
6)第六步:看是否有执行计划突变
有时 SQL 本来没问题,但因为:
- 统计信息过期
- 数据分布变化
- 优化器选错索引
导致执行计划突然变差,CPU 飙升。
所以有时要对比:
- 正常时执行计划
- 出问题时执行计划
7)现场处理怎么做?
(1)先止血
如果确定是某些 SQL 导致,可以临时:
KILL <thread_id>;把高消耗线程杀掉。
当然要谨慎:
- 确认不是核心事务
- 先和业务确认影响
(2)限流
如果是突发流量或恶意请求:
- 应用层限流
- 网关限流
- 降级某些接口
- 限制连接数
(3)临时切流量
如果有读写分离、从库、缓存层,可临时分流部分查询压力。
8)后续优化怎么做?
根据原因不同处理:
情况一:SQL 问题
- 加索引
- 改 SQL
- 减少回表
- 减少大分页 / 大排序
情况二:并发太高
- 做缓存
- 接口限流
- 读写分离
- 提升数据库资源
情况三:执行计划问题
- 更新统计信息
- 调整索引
- 必要时加 hint(谨慎)
情况四:业务流量异常
- 排查上游调用
- 修复重试机制
- 防止雪崩
9)总结
MySQL CPU 飙升的排查思路:
- 先确认是不是
mysqld - 看
processlist - 找高消耗 SQL
EXPLAIN分析执行计划- 看连接数和流量是否异常
- 必要时 kill 线程止血
- 后续从 SQL、索引、流量、架构角度优化
一句话总结:
CPU 飙升通常不是“数据库坏了”,而是数据库在高成本执行某些请求,关键是快速定位“哪类 SQL 或哪波流量”导致。