2919 字
15 分钟
100 道常见 MySQL 数据库题目运维篇
2026-04-16 17:49:34
无标签

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 orders
WHERE id >= 1 AND id < 1001;

然后循环往下删。

或者:

DELETE FROM orders
WHERE create_time < '2023-01-01'
LIMIT 1000;

注意:如果用 LIMIT 删除,最好结合有序主键或索引条件,否则可能效率很差。


3)为什么分批删除更安全?#

因为每次删除的数据量小,可以做到:

  • 事务短
  • 锁持有时间短
  • redo / undo / binlog 压力小
  • 对线上影响更小
  • 可以随时暂停

这是生产环境里最实用的方法。


4)分批删除的一般建议#

(1)优先按主键范围删#

因为主键通常有序,执行更稳定。

例如:

DELETE FROM orders
WHERE id BETWEEN 100001 AND 101000;

(2)每批大小要控制#

常见经验值:

  • 500
  • 1000
  • 2000

具体要看:

  • 机器性能
  • 表大小
  • 业务峰值
  • 主从同步情况

(3)删除批次之间适当 sleep#

例如每删一批暂停几十毫秒 / 几百毫秒,减少对线上冲击。


5)如果删除的是历史数据,更好的方式是什么?#

如果是按时间删除大量历史数据,且表结构允许,通常更推荐:

方案一:分区表 + 删除分区#

如果表按时间分区,那么删除历史数据时直接:

ALTER TABLE ... DROP PARTITION ...

这比逐行 delete 快得多。


方案二:归档后重建表#

如果要删的数据特别多,甚至占表的大部分,有时会考虑:

  1. 建新表
  2. 只导入要保留的数据
  3. 替换旧表

这种方式本质上是“保留少量、重建整表”,比删除海量数据更高效。 不过实现复杂,需要评估停机或灰度迁移方案。


6)能不能先删索引再删数据?#

这个说法在某些离线场景下成立,但在线业务表一般要非常谨慎。

理论上:

  • 删除数据时索引越多,维护成本越高
  • 所以先删索引再删数据,速度可能更快

但在线生产环境通常不这么干,因为:

  • 删索引本身就有风险
  • 删完索引可能影响线上查询
  • 重建索引也可能很重

所以生产里更常见的仍然是:

  • 分批删除
  • 限速执行
  • 错峰操作

7)删除后空间会立刻释放吗?#

通常不会。 InnoDB 删除记录后,空间一般是:

  • 标记为可复用
  • 不是立刻归还操作系统

如果想真正缩小表文件,可能要:

  • OPTIMIZE TABLE
  • 重建表
  • ALTER TABLE ... ENGINE=InnoDB

但这些操作通常代价不小,要谨慎。


8)总结#

删除百万级数据的正确姿势:

  1. 不要一条大 DELETE
  2. 优先按主键 / 索引分批删除
  3. 每批控制数量
  4. 分批之间适当休眠
  5. 错峰执行
  6. 必要时考虑分区表或重建表方案

一句话总结:

大数据量删除的核心原则是:小批量、可中断、低冲击。


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 提供的在线改表工具。


原理大致是:#

  1. 新建一张结构变更后的临时表
  2. 在旧表上建触发器
  3. 把旧表数据分批拷贝到新表
  4. 拷贝期间通过触发器同步增量变更
  5. 数据追平后,最终原子性 rename 切换新旧表

优点#

  • 对线上影响相对较小
  • 适合大表在线改结构

缺点#

  • 实现复杂
  • 会用到触发器
  • 需要评估主从复制和业务兼容性

4)常见方案三:先改从库,再主从切换#

如果系统有主从架构,可以考虑:

  1. 先在从库做表结构变更
  2. 验证从库正常
  3. 主从切换
  4. 再处理原主库

这种方式适合:

  • 有完整主从切换能力
  • 业务能接受切换窗口
  • DBA 运维能力较强

5)常见方案四:新建表迁移#

对于特别大的表,有时会采用“新表迁移”方案:

  1. 新建带新字段的表
  2. 历史数据迁移
  3. 增量双写
  4. 校验
  5. 切换流量
  6. 下线旧表

这和不停机扩容思路很像,复杂但风险可控。


6)如果字段允许,优先加可空字段#

通常加一个:

  • 可空字段
  • 没有复杂默认值
  • 末尾追加字段

相对风险更低。

例如:

ALTER TABLE user ADD COLUMN remark VARCHAR(255) NULL;

往往比:

  • 非空字段
  • 带复杂默认值字段
  • 中间插入字段

更容易在线执行。


7)上线前要做什么准备?#

(1)先在测试环境演练#

模拟真实数据量,估算执行时间。

(2)检查长事务#

避免 DDL 被 MDL 锁卡住。

(3)选择低峰时段#

减少业务冲击。

(4)关注主从延迟#

尤其在线上主从架构中。

(5)有回滚预案#

DDL 一旦执行,回滚成本可能很高,所以必须有兜底方案。


8)总结#

大表加字段常见可行方案有:

  1. Online DDL
  2. pt-online-schema-change
  3. 先改从库再切换
  4. 新表迁移双写切换

一句话总结:

大表加字段的关键不是 SQL 本身,而是如何在不阻塞业务的情况下安全变更表结构。


66. MySQL 数据库 CPU 飙升的话,要怎么处理呢?#

答:

MySQL CPU 飙升通常说明数据库正在执行高消耗操作。 这类问题排查要分两步:

  1. 先确认是不是 MySQL 导致
  2. 再定位到底是哪类 SQL / 哪类操作导致

这是典型线上故障排查题,回答时建议按“排查流程 + 处理手段”来讲。


1)第一步:先确认是不是 mysqld 导致#

先在机器上用:

Terminal window
top

或者:

Terminal window
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 BY
  • GROUP BY
  • DISTINCT
  • 大结果集排序
  • 临时表操作

导致 CPU 使用很高。

这类 SQL 往往在 EXPLAINExtra 中能看到:

  • Using temporary
  • Using filesort

6)第六步:看是否有执行计划突变#

有时 SQL 本来没问题,但因为:

  • 统计信息过期
  • 数据分布变化
  • 优化器选错索引

导致执行计划突然变差,CPU 飙升。

所以有时要对比:

  • 正常时执行计划
  • 出问题时执行计划

7)现场处理怎么做?#

(1)先止血#

如果确定是某些 SQL 导致,可以临时:

KILL <thread_id>;

把高消耗线程杀掉。

当然要谨慎:

  • 确认不是核心事务
  • 先和业务确认影响

(2)限流#

如果是突发流量或恶意请求:

  • 应用层限流
  • 网关限流
  • 降级某些接口
  • 限制连接数

(3)临时切流量#

如果有读写分离、从库、缓存层,可临时分流部分查询压力。


8)后续优化怎么做?#

根据原因不同处理:

情况一:SQL 问题#

  • 加索引
  • 改 SQL
  • 减少回表
  • 减少大分页 / 大排序

情况二:并发太高#

  • 做缓存
  • 接口限流
  • 读写分离
  • 提升数据库资源

情况三:执行计划问题#

  • 更新统计信息
  • 调整索引
  • 必要时加 hint(谨慎)

情况四:业务流量异常#

  • 排查上游调用
  • 修复重试机制
  • 防止雪崩

9)总结#

MySQL CPU 飙升的排查思路:

  1. 先确认是不是 mysqld
  2. processlist
  3. 找高消耗 SQL
  4. EXPLAIN 分析执行计划
  5. 看连接数和流量是否异常
  6. 必要时 kill 线程止血
  7. 后续从 SQL、索引、流量、架构角度优化

一句话总结:

CPU 飙升通常不是“数据库坏了”,而是数据库在高成本执行某些请求,关键是快速定位“哪类 SQL 或哪波流量”导致。

Comment seems to stuck. Try to refresh?✨