背景
为啥MySQL不建议用DELETE删数据?说白了,你可能还真不配用它
很多人看到这里可能不禁疑惑了,那不用Delete删除数据,那用什么删除数据呢?
阿里面试问这个问题,还真不是刁难你,是想看看你到底是CRUD民工,还是能把数据库当朋友用的老炮儿。
DELETE听起来很清爽,用起来往往“祭天”
首先,DELETE不是不能用,而是你得知道它的代价,你得知道它背后到底做了啥。很多人写个SQL就是:
DELETE FROM user WHERE status = 'inactive';
看着清清爽爽,执行完了控制台还给你来句“Query OK, 1000000 rows affected”,然后服务嘎的一声宕机,QPS跌成狗,DBA头发开始谢落,领导问你是不是手滑了。
这波操作,线上的锅你得背,简历得更新,关键是你还不知道错哪了。
MySQL DELETE底层到底干了啥?
MySQL的DELETE,其实背地里非常重,尤其是你删的是InnoDB表,它做的事情比你以为的多得多:
1. DELETE不是物理删除,而是行级删除 + 日志记录
InnoDB是支持事务的,它不能说删就删,得有回滚能力。所以它做了这些事:

- 标记要删除的行
- 写UNDO日志(为了事务回滚)
- 写REDO日志(为了崩了还能恢复)
- 更新索引(主键+二级索引全部都得动手)
- 最后才是真正的标记行已删除 你以为你在“清空”,其实你在走一整套事务机制,跟离婚一样,得先申请、备案、调解、走流程,最后判离。全表DELETE就相当于办集体离婚,政务大厅都能爆了。
2. 删除的行不会马上释放空间
InnoDB做的是MVCC(多版本并发控制),DELETE之后的数据并不会立马消失,旧版本还留着,事务没提交前你还能查出来。
即便提交了,磁盘空间也不会释放。你DELETE 100W 行,磁盘占用可能一个字节都不降。你得等它自己触发PURGE流程,或者你主动做OPTIMIZE TABLE,才能回收。

大哥你删了还得自己收拾战场,这叫啥?这叫“你杀人你还得擦血迹”,你说累不累。
DELETE的几个雷?
1. DELETE大表,直接锁死线上系统
某年某月某日,一个实习生(不是我)要清除3个月前的订单,写了个:
DELETE FROM orders WHERE create_time < '2023-01-01';
然后按下执行,MySQL瞬间CPU飙升,表锁死,所有查订单的接口直接502。客服在工位上哭,运维在电话里骂,领导进会议室打我(虽然不是我干的)。
问题在哪?
- 没分页
- 没用LIMIT
- 没用索引(是的,create_time没建索引)
- 没归档策略
- 一条SQL,把整个库怼瘫了 DELETE默认是行锁,但你删太多行,它可能升级为表锁,你那点小QPS根本扛不住。
2. DELETE会拖垮redo log
InnoDB写redo log有缓冲区,有write-ahead机制,但DELETE一多,日志量陡增,缓存直接爆掉,IO跟不上,写盘阻塞。
你以为是DELETE卡住了,其实是磁盘在疯狂转,binlog、undo log、redo log都挤在那排队。
3. DELETE影响主从延迟
大厂主从架构都跑得飞快,但DELETE一跑,binlog刷一堆,从库延迟直接上天。
主库写了一个小时,从库还在回放那条SQL。你要是还有读写分离,那这时候读到的数据都是旧的,订单显示“未支付”,其实人家早付款了,分分钟投诉你虚假交易。
为啥阿里不建议用DELETE?
别说阿里,字节、腾讯、京东都一样,对DELETE有明确要求。
像阿里的数据量,一张表动辄上亿数据。
你一句DELETE,全删了,分库分表的路都白走了,业务线可能一个月都恢复不过来。
所以在大厂,DELETE基本上有以下规定:
- 禁止在主库DELETE超过N条记录
- DELETE必须带LIMIT,必须走索引
- 大表DELETE必须走归档逻辑,不允许直接删
- 定时归档走独立脚本 + 延迟消费
- 回收策略用DROP分表 + 重建表
而不是写个DELETE FROM xxx WHERE xxx,那是作死速通路线。
那不用DELETE,咋清数据?
1. DELETE + LIMIT + SLEEP 慢慢删
这个是经典套路:
-- 分批删除,每次只删1000条 DELETE FROM orders WHERE create_time < '2023-01-01' LIMIT 1000; -- 循环 + SLEEP 1s 避免IO暴冲
写成存储过程、脚本,跑定时任务,慢慢挪,你像倒垃圾一样,别全往楼下扔,分批分类处理。
2. 归档转移 + 表切换
- 新表 orders_archive
- insert into orders_archive select * from orders where create_time < xxx;
- insert成功后,DELETE or TRUNCATE or DROP老数据
3. TRUNCATE + 表分区 / 分表策略
你想清空数据,直接TRUNCATE,比DELETE干净利索。但条件是:
- 表不能有外键
- 你要敢直接扔整个表
那咋办?用分表。
订单表 orders_202406、orders_202405、orders_202404
清理2024年4月的订单?直接DROP TABLE orders_202404,嘎嘣脆,不走DELETE逻辑,干净直接。
DELETE不是错,错的是你没搞清楚你的库值多少钱
MySQL是你用的最久的朋友,却也最容易被你轻视。你说DELETE就是删除,那你可真把自己当CRUD Boy了。
你得问问自己:
- 这张表多大?
- 有多少行?
- delete操作是否走索引?
- 有没有binlog回放压力?
- 是主库还是从库执行?
- 是否有归档机制?
你不搞清楚这些,就动手删?那跟不看红绿灯上高速一个意思,迟早出事故。
说在最后
阿里面试问这个问题,其实是想看看你对数据库有没有“敬畏之心”。不是说你不能删,而是你得知道,你删的不是数据,你删的是稳定,是成本,是流量,是你同事的加班时间。