delete in (select …) 语句执行慢,delete语句不走索引。

在清理MySQL中的一些过期数据时,发现delete操作执行非常慢。

问题场景

需要清理的数据查询统计结果如下:

1
2
3
4
5
6
7
mysql> select count(1) from t_strategy_detail where id in (select strategy from robots where stop_time<1699782290 AND stop_kind > 0 AND stop_kind < 100);
+----------+
| count(1) |
+----------+
|   503902 |
+----------+
1 row in set (1.75 sec)

开始以为是清理的数据太多导致,所以把清理范围缩小:

1
2
3
4
5
6
7
mysql> select count(1) from t_strategy_detail where id in (select strategy from robots where stop_time<1629782290 AND stop_kind > 0 AND stop_kind < 100);
+----------+
| count(1) |
+----------+
|     1779 |
+----------+
1 row in set (0.01 sec)

缩小清理范围后,发现删除操作执行依然很慢,执行语句如下:

1
2
mysql> delete from t_strategy_detail where id in (select strategy from robots where stop_time<1629782290 AND stop_kind > 0 AND stop_kind < 100);

这个时候意识到和数据大小无关,应该是执行语句存在问题,立即分析执行SQL。

问题分析

执行计划分析结果如下:

1
2
3
4
5
6
7
8
mysql> explain delete from t_strategy_detail where id in (select strategy from robots where stop_time<1629782290 AND stop_kind > 0 AND stop_kind < 100);
+----+--------------------+------------------+------------+-------+---------------------------------+-----------------+---------+------+--------+----------+------------------------------------+
| id | select_type        | table            | partitions | type  | possible_keys                   | key             | key_len | ref  | rows   | filtered | Extra                              |
+----+--------------------+------------------+------------+-------+---------------------------------+-----------------+---------+------+--------+----------+------------------------------------+
|  1 | DELETE             | t_strategy_detail | NULL       | ALL   | NULL                            | NULL            | NULL    | NULL | 454862 |   100.00 | Using where                        |
|  2 | DEPENDENT SUBQUERY | robots           | NULL       | range | index_stop_time,index_stop_kind | index_stop_time | 5       | NULL |   2180 |     5.00 | Using index condition; Using where |
+----+--------------------+------------------+------------+-------+---------------------------------+-----------------+---------+------+--------+----------+------------------------------------+
2 rows in set (0.00 sec)

可以看到,子查询是有走索引的,可是delete语句是全表扫描,这是为什么呢,delete条件是主键匹配,为什么不走主键索引?

把语句修改为查询操作分析,查询语句有走索引

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
mysql> explain select * from t_strategy_detail where id in (select strategy from robots where stop_time<1629782290 AND stop_kind > 0 AND stop_kind < 100);

+----+--------------+------------------+------------+--------+---------------------------------+-----------------+---------+----------------------+------+----------+------------------------------------+
| id | select_type  | table            | partitions | type   | possible_keys                   | key             | key_len | ref                  | rows | filtered | Extra                              |
+----+--------------+------------------+------------+--------+---------------------------------+-----------------+---------+----------------------+------+----------+------------------------------------+
|  1 | SIMPLE       | <subquery2>      | NULL       | ALL    | NULL                            | NULL            | NULL    | NULL                 | NULL |   100.00 | Using where                        |
|  1 | SIMPLE       | t_strategy_detail | NULL       | eq_ref | PRIMARY                         | PRIMARY         | 4       | <subquery2>.strategy |    1 |   100.00 | NULL                               |
|  2 | MATERIALIZED | robots           | NULL       | range  | index_stop_time,index_stop_kind | index_stop_time | 5       | NULL                 | 2181 |    50.00 | Using index condition; Using where |
+----+--------------+------------------+------------+--------+---------------------------------+-----------------+---------+----------------------+------+----------+------------------------------------+
3 rows in set, 1 warning (0.00 sec)

修改为select后,可以看到主查询是走主键索引的。

在网上找到一些相同的问题分析,问题原因大概是,MySQL对UPDATE,DELETE子查询的语句不会优化,推荐改为JOIN的方式执行。

优化方案

还有一种简便的方案,就是给表加别名,加别名也会触发MySQL优化策略。

优化方案执行计划分析结果如下:

1
2
3
4
5
6
7
8
9
mysql> explain delete s from t_strategy_detail as s where s.id in (select strategy from robots where stop_time<1629782290 AND stop_kind > 0 AND stop_kind < 100);
+----+--------------+-------------+------------+--------+---------------------------------+-----------------+---------+----------------------+------+----------+-------------+
| id | select_type  | table       | partitions | type   | possible_keys                   | key             | key_len | ref                  | rows | filtered | Extra       |
+----+--------------+-------------+------------+--------+---------------------------------+-----------------+---------+----------------------+------+----------+-------------+
|  1 | SIMPLE       | <subquery2> | NULL       | ALL    | NULL                            | NULL            | NULL    | NULL                 | NULL |   100.00 | Using where |
|  1 | DELETE       | s           | NULL       | eq_ref | PRIMARY                         | PRIMARY         | 4       | <subquery2>.strategy |    1 |   100.00 | NULL        |
|  2 | MATERIALIZED | robots      | NULL       | range  | index_stop_time,index_stop_kind | index_stop_time | 5       | NULL                 | 2181 |    50.00 | Using where |
+----+--------------+-------------+------------+--------+---------------------------------+-----------------+---------+----------------------+------+----------+-------------+
3 rows in set (0.00 sec)

可以看到delete操作是有走索引的。

修改为别名后执行很快,执行结果如下:

1
2
mysql> delete s from t_strategy_detail as s where s.id in (select strategy from robots where stop_time<1629782290 AND stop_kind > 0 AND stop_kind < 100);
Query OK, 1779 rows affected (0.04 sec)

扩大清理范围相对之前要快很多,执行结果如下:

1
2
mysql> delete s from t_strategy_detail as s where s.id in (select strategy from robots where stop_time<1699782290 AND stop_kind > 0 AND stop_kind < 100);
Query OK, 502123 rows affected (9.53 sec)

最后清理子查询数据:

1
2
mysql> delete from robots where stop_time<1699782290 AND stop_kind > 0 AND stop_kind < 100;
Query OK, 503902 rows affected (33.82 sec)

参考