【每天5分钟,了解一个知识点】
一、MySQL 的存储结构与 DELETE 的影响
InnoDB 的存储结构较为复杂,其将数据划分为若干个页,页的大小默认是 16KB,是磁盘和内存交互的基本单位。数据库中还存在区、段和表空间的概念。区是比页大一级的存储结构,一个区会分配 64 个连续的页,大小为 1MB。段由一个或多个区组成,是数据库中的分配单位,比如创建表和索引时会创建相应的段。表空间是一个逻辑容器,可以有一个或多个段。

DELETE 操作在 InnoDB 存储引擎中只是将数据标记为删除,并不真正释放物理空间。这是因为 InnoDB 采用了 B + 树作为存储数据的结构,删除数据时,会有两种情况。如果删除数据页中的某些记录,会将记录标记为可复用的位置,当某次插入数据 ID 符合时,就会复用该位置。如果删除整个数据页的内容,会将整个页标记为可复用。但无论是哪种情况,磁盘文件的大小并不会减少。
这种标记删除的机制会导致表文件大小不变甚至增大,同时还会产生大量的碎片。碎片不仅会占用大量可用空间,造成空间浪费,还会降低读写性能。由于存在大量碎片,数据从连续规则的存储方式变为随机分散的存储方式,会增加磁盘 IO 的负担,导致读写性能下降。例如,在一个经过大量增删改的表中,可能存在很多空洞,这些空洞就是碎片。当有新的数据插入时,这些空洞未必能匹配得上新的数据大小,造成了表空间内的严重浪费。久而久之,SQL 的执行速度会受这些碎片影响,变得越来越慢。
二、DELETE 带来的问题
(一)空间不释放DELETE 语句在执行时,只是对数据进行了标记删除,数据并不会从磁盘上立即消失。在 InnoDB 存储引擎中,这种标记删除的机制虽然保证了数据还能在事务回滚时恢复,但同时也意味着,数据物理上还占着地方呢。例如,当我们使用 DELETE 语句删除一大批数据时,文件大小也不会立刻减少,甚至很多时候删除了一大堆数据,表的文件大小却丝毫未动。
(二)碎片问题DELETE 数据后,表里会产生大量的 “空洞”。这些被标记为已删除的数据并不会被立即重用,下次有新的数据插入时,这些空洞未必能匹配得上新的数据大小,造成了表空间内的严重浪费。随着时间的推移,表空间内的碎片越来越多,SQL 的执行速度会受这些碎片影响,变得越来越慢。据统计,在经过大量的增删改操作后,表的碎片率可能会高达 30% 甚至更高,这将极大地影响数据库的性能。
(三)回收碎片的性能开销虽然我们可以用 OPTIMIZE TABLE 来回收这些空间,但这会消耗大量的 I/O 资源。执行这个命令相当于搞了一次大手术,它需要重建整个表,重新分配所有数据页。这一过程中会占用大量的 CPU 和 I/O,直接影响系统性能。例如,在一个拥有数百万条记录的大表上执行 OPTIMIZE TABLE 命令,可能需要数小时甚至更长时间,在此期间,数据库的响应时间会变得非常长,严重影响系统的正常运行。
(四)影响查询性能当我们在表中用 DELETE 删除了大量数据之后,这些数据虽然从逻辑上 “没了”,但实际上还留在数据页里。接下来如果有查询操作,MySQL 在扫描数据时,会把那些标记为 “已删除” 的数据也给扫描一遍。这意味着你实际上在查询的是一张更大的表,耗费了更多的 I/O。长期这样下去,表的响应时间会变得越来越慢,性能自然也就越来越差。假设一个表中有 100 万条数据,使用 DELETE 删除了 50 万条后,即使查询只需要扫 5 万条数据,MySQL 依然要扫描整个数据页的 100 万条记录,这就大大增加了查询时间。随着数据量的继续增加,查询性能将直线下滑。
三、不同观点的争论
(一)软删除派
软删除的好处在于你可以随时恢复数据,尤其是对于那些需要保留历史记录的业务来说,这种方式能确保数据完整性。比如在一些金融交易系统中,每一笔交易记录都可能需要在未来进行审计或查询,软删除可以保证这些数据在需要的时候能够被轻松找回。你可以给每条记录加一个 is_deleted 字段,当这条记录被 “删除” 时,is_deleted 的值从 0 变成 1。查询时,只需要加个条件 WHERE is_deleted = 0 就能过滤掉被删除的数据。这种方式在实际应用中较为常见,据统计,约有 40% 的企业级应用在处理数据删除时会考虑软删除的方式。但是软删除也有它的弊端。首先是数据量膨胀问题,由于 “删除” 后的数据并没有真正删除,表里的数据量会持续膨胀。对某些高并发场景,这种方式显然不太适用。例如在电商平台的订单系统中,如果采用软删除,随着时间的推移,订单数据会不断积累,可能会导致数据库性能下降。其次是索引失效问题。随着数据量的增加,查询性能下降。这时即使有索引,也可能变得没什么用了,毕竟 MySQL 还是得扫描大量 “死数据” 来做过滤。在一个拥有百万级数据量的表中,如果采用软删除,索引的效果可能会大打折扣,查询时间可能会增加数倍甚至更多。
(二)硬删除派
硬删除的支持者则主张直接把数据彻底清理掉。这样表的大小可以实时减小,不会有 “死数据” 影响性能。比如在一些日志系统中,过期的日志数据可能不再需要,硬删除可以及时释放空间,提高数据库的性能。然而,硬删除在实际操作中也有挑战,特别是数据量非常大的情况下,一次性删太多数据,数据库负载可能瞬间飙升,引发系统不稳定。据测试,在一个拥有千万级数据量的表中,如果一次性进行硬删除操作,可能会导致数据库的 CPU 使用率瞬间升高到 80% 以上,甚至可能导致数据库崩溃。在实际应用中,需要根据具体的业务场景和需求来选择合适的删除方式。如果对数据的完整性和可恢复性要求较高,可以考虑软删除;如果更注重数据库的性能和空间利用率,并且能够承受一定的风险,硬删除可能是一个选择。但无论选择哪种方式,都需要谨慎操作,确保数据库的稳定运行。
四、解决方案及建议
(一)阿里的做法
在阿里这种超大规模数据场景下,DELETE 已经不再是他们的首选。他们会对大表进行分区管理,把旧数据定期归档到冷存储里,然后定期清理这些冷数据。
具体来说,阿里采用分区表的方式,将数据按时间段或者业务类型进行分区管理。例如每天的数据就存在一个分区里,到了一定的时限,比如三个月后,把这些分区的数据直接归档到冷存储。这样可以避免让 MySQL 扫描无效的过期数据。
同时,阿里还会将过期的数据移到归档表里,而不是直接删除。这样可以保证万一出现业务问题,还能随时把这些数据还原回来。并且,阿里会定期清理这些冷数据,对那些确实已经没用的数据,定期使用批量删除的方式进行清理,别一次性删太多,控制删除频率。
(二)给用户的建议
对于普通用户来说,也可以借鉴阿里的做法。
首先,可以采用分区表的方式,把数据按时间段或者业务类型进行分区管理。这样可以提高查询效率,避免扫描不必要的数据。
其次,可以建立归档表,把过期的数据移到归档表里,而不是直接删除。这样既可以保证数据的安全性,又可以释放一部分空间。
再者,要定期清理数据,对那些确实已经没用的数据,定期使用批量删除的方式进行清理,控制删除的频率和数量,避免一次性删太多数据导致数据库负载飙升。
此外,还需要注意碎片整理。就算不直接删除数据,系统中还是会不可避免地产生碎片。
这个时候,可以考虑定期进行碎片整理,也就是使用 OPTIMIZE TABLE 或者 ANALYZE TABLE 命令来回收表中的碎片。但要注意,这个操作非常耗资源,建议选择业务低峰期进行。
总之,在处理大表或者对性能要求极高的场景下,尽量不要直接用 DELETE。而是要根据实际情况选择合适的方案,如软删除、分区表、归档数据等,这样才能更好地管理数据,让数据库运行得更顺畅。
【关联阅读】
关注公众号,回复【Java面试】,获取更多面试资料




