数据库

为啥MySQL不建议用DELETE删数据?

转载:阿里面试:为啥MySQL不建议用DELETE删数据?

背景


为啥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. 归档转移 + 表切换

  1. 新表 orders_archive
  2. insert into orders_archive select * from orders where create_time < xxx;
  3. 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回放压力?
  • 是主库还是从库执行?
  • 是否有归档机制?

你不搞清楚这些,就动手删?那跟不看红绿灯上高速一个意思,迟早出事故。

说在最后


阿里面试问这个问题,其实是想看看你对数据库有没有“敬畏之心”。不是说你不能删,而是你得知道,你删的不是数据,你删的是稳定,是成本,是流量,是你同事的加班时间。