导读:本期聚焦于布兰登创作的《SQL DELETE删除速度太慢?为WHERE筛选列创建索引的优化思路》,敬请观看详情。一张千万级数据表执行DELETE语句时,明明只删除几百行,却耗时几十秒甚至更久,这种现象通常不是删除动作本身的开销,而是WHERE条件无法快速定位目标行,导致数据库走了全表扫描。全表扫描意味着存储引擎需要逐行比对筛选条件,数据量越大,扫描成本越高,同时还会持有更长时间的锁,进一步拖慢并发操作。解决这类问题的核心思路之一,就是为WHERE子句中用于筛选的列创建合适的索引。索引能够将定位目标行的过程从线性扫描变成基于B+树结构的快速查找,大幅减少需要访问的数据页数量。不过在删除场景下创建索引也存在维护成本,并且复合列、索引选择性、批量删除策略都会影响最终效果。本文会结合执行计划、索引选择原则和实际SQL示例,梳理如何判断是否需要建索引以及怎样避免删除操作引发新的性能问题。

SQL 的 DELETE 语句执行缓慢通常不是删除动作本身的问题,而是数据库在 WHERE 条件中无法高效定位要删除的行。以一张千万级的订单表为例,如果 status 和 created_at 字段上没有索引,下面的删除语句可能触发全表扫描:

DELETE FROM orders WHERE status = 'CANCELLED' AND created_at < '2024-01-01';

SQL DELETE删除速度太慢?为WHERE筛选列创建索引的优化思路

此时即使最终只删除几百行,存储引擎也可能需要扫描数百万甚至上千万行数据来逐条判断。扫描过程不仅拉长执行时间,还会让事务持有更长时间的锁,影响其他写入和查询。解决这个问题的第一步,通常是检查 WHERE 筛选列是否具备合适的索引。

为什么DELETE会慢:从全表扫描说起

在 InnoDB 存储引擎中,DELETE 语句的执行流程可以分为两个阶段:定位目标行和实际删除。定位目标行时,优化器会依据 WHERE 条件、可用索引和统计信息选择访问路径。如果 WHERE 涉及到的列没有索引,或者索引选择性太差,优化器往往会选择全表扫描。全表扫描意味着从主键索引的叶子链表开始,按顺序读取大量数据页,逐行比对条件,直到遍历完整张表。

全表扫描的成本随着数据量线性增长。比如一张 1000 万行的表,哪怕只删除符合条件的三行,也要把这 1000 万行全部读进缓冲池或磁盘。与此同时,InnoDB 默认在扫描到的行上加锁,虽然最终只删除少数行,但扫描过程中可能对大量记录加锁,从而阻塞其他事务的插入、更新和删除操作。这种锁范围扩大是很多线上数据库出现锁等待和连接堆积的诱因。

可以通过执行计划快速验证是否走了全表扫描。将 DELETE 改写成同等条件的 SELECT,再使用 EXPLAIN 查看:

EXPLAIN SELECT id FROM orders
WHERE status = 'CANCELLED' AND created_at < '2024-01-01';

如果输出中 type=ALL、key=NULL、rows 接近表总行数,就说明没有可用的索引,优化器只能全表扫描。此时先不要急着增加硬件资源,而是考虑为 WHERE 筛选列创建索引。

为WHERE筛选列创建索引的核心思路

索引的本质是建立一种有序的数据结构,让数据库可以快速跳过不符合条件的记录。在 InnoDB 中,二级索引通常以 B+ 树组织,叶子节点保存索引列的值和对应主键。当 WHERE 条件中的列被索引覆盖时,优化器可以从根节点开始,通过二分查找快速定位到满足条件的第一个叶子节点,然后沿着叶子链表向后扫描,直到条件不满足为止。这种访问方式称为 range 或 ref,相较于全表扫描,读取的数据页数量会大幅下降。

创建索引前需要分析 WHERE 条件的具体形态。如果条件中包含多个列,例如 status 和 created_at 同时过滤,一般优先考虑复合索引,而不是给每个列单独建索引。复合索引的顺序很关键,遵循最左前缀原则。对于上面的语句,status 是等值条件,created_at 是范围条件,建议创建复合索引:

CREATE INDEX idx_orders_status_created ON orders (status, created_at);

这样优化器可以先通过 status 定位到所有已取消订单,再在 created_at 范围内快速筛选,避免了回表后再判断 created_at。如果反过来把 created_at 放在复合索引前面,由于范围条件会中断后续列的使用,status 就无法有效利用该索引,查询效率会打折扣。因此复合索引的列顺序应遵循等值条件在前、范围条件在后的原则。

单独给 status 建索引是否可行?要看 status 的选择性。如果 status 只有几个固定值,例如 PENDING、PAID、CANCELLED,其中 CANCELLED 占比不高,那么单独 status 索引可以帮助定位,但定位后仍需在大量行中过滤 created_at 范围,效率不如复合索引。如果某个状态占比超过全表的 20% 到 30%,优化器可能认为回表成本过高,仍然选择全表扫描。因此索引是否生效,最终还要以 EXPLAIN 的结果为准。

实战验证:创建索引前后的执行计划对比

假设 orders 表有约 800 万行数据,其中 status 等于 CANCELLED 且 created_at 早于 2024 年的记录约有 2000 条。创建任何索引之前执行 EXPLAIN,可能会看到类似这样的结果:

EXPLAIN SELECT id FROM orders
WHERE status = 'CANCELLED' AND created_at < '2024-01-01';

type 为 ALL,possible_keys 为 NULL,key 为 NULL,rows 约为 8000000。这说明优化器认为没有索引可以帮助缩小范围,只能扫描整张表。此时执行 DELETE 往往非常慢,并且可能产生大量锁。

创建 idx_orders_status_created 索引后再次执行 EXPLAIN,输出会发生明显变化:type 可能变成 range,key 显示 idx_orders_status_created,rows 可能缩小到 2000 左右。这意味着优化器只需要扫描符合条件的 2000 行左右,而不是全表的 800 万行。实际 DELETE 时间通常从几十秒甚至几分钟下降到毫秒到秒级。

如果创建索引后优化器仍未使用,可以检查统计信息是否过期。尤其是大表频繁插入删除后,InnoDB 的统计信息可能不准确,导致优化器误判。可以对表执行 ANALYZE TABLE 更新统计信息,或者使用 FORCE INDEX 临时强制走索引进行测试,但生产环境不建议长期强制指定索引。

ANALYZE TABLE orders;

删除场景中的索引维护与批量删除策略

为 WHERE 列创建索引可以显著加速定位过程,但 DELETE 并不会因此完全没有代价。删除一行数据时,InnoDB 除了删除主键索引中的记录,还需要同步删除所有二级索引中的对应条目。如果一张表上存在大量二级索引,每次 DELETE 都会对这些索引进行维护,删除操作本身的开销会上升。因此在频繁删除的表上,索引数量并非越多越好,只需要为真正参与 WHERE、JOIN、ORDER BY 和 GROUP BY 的列创建索引。

如果一次性删除的数据量非常大,即使已经使用索引,也不建议在一个事务中直接执行全量 DELETE。大事务会导致 undo 日志膨胀、锁持有时间过长,还可能在主从复制场景下产生明显延迟。比较稳妥的做法是分批删除,每批处理几百到几千行,并在每批之间适当停顿或提交事务。例如:

DELETE FROM orders
WHERE status = 'CANCELLED' AND created_at < '2024-01-01'
LIMIT 1000;

重复执行这条语句,直到 affected rows 为 0。这样每批事务较小,锁的持有时间短,对线上其他操作的影响也更可控。如果业务上只是定期清理历史数据,还可以考虑用分区表替换大表删除,或先将数据写入归档表再删除,进一步降低在线库的压力。

另外要避免在 WHERE 条件中对索引列使用函数或表达式。例如 DATE(created_at) < '2024-01-01' 会导致 created_at 上的索引失效,因为优化器无法直接使用索引树进行范围查找。正确做法是使用原始列的范围条件,例如 created_at < '2024-01-01'。同理,类型不一致的隐式转换也可能导致索引失效,比如用字符串条件过滤数值列时尽量保持类型一致。

SQL DELETE优化索引优化删除性能修改时间:2026-08-30 21:02:02

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。