针对 DELETE 查询的 MySQL 性能调整

Posted

技术标签:

【中文标题】针对 DELETE 查询的 MySQL 性能调整【英文标题】:MySQL performance tuning for DELETE query 【发布时间】:2020-02-19 11:07:48 【问题描述】:

谁能帮我重新编写查询以加快执行时间?执行时间 37 秒。

DELETE FROM storefront_categories 
WHERE userid IN (SELECT userid 
                FROM MASTER 
                where expirydate<'2020-2-4' 
                )

同时,这个查询只用了 4.69 秒就执行完毕。

DELETE FROM storefront_categories 
WHERE userid NOT IN (SELECT userid FROM MASTER)

storefront_categories 表有 97K 条记录,而 MASTER 表有 40K 条记录。我们在 MASTER.expirydate 字段上创建了一个索引。

【问题讨论】:

如果要删除的记录太多,可以批量使用(除了加索引加快查询)。即,在查询中附加一个限制,例如“LIMIT 1000”。 你要删除 40K 行吗? 否,应用 WHERE 条件删除后最多 10-20 条记录。 【参考方案1】:

删除 40K 行时,预计会花费一些时间。主要成本(假设有足够的索引和体面的查询)是“原子”删除的事务语义的开销。这包括制作每行被删除的副本,以防万一发生崩溃。这样,InnoDB 可以将数据库恢复到崩溃前的状态。

当删除一个表的 40% 时,将行复制到另一个表中比交换表要快得多。

删除大量行时(不考虑百分比),最好分块进行。并且最好根据PRIMARY KEY遍历表。

我在http://mysql.rjweb.org/doc.php/deletebig 中讨论了这两种技术以及其他技术

关于查询公式:

取决于版本;旧版本的 MySQL 在某些方面表现不佳。 NOT IN (SELECT ...)NOT EXISTS 往往表现最差。 IN (SELECT ...) 和/或 EXISTS 可能会更好。 “多表DELETE 是另一种选择。它的工作方式类似于JOIN。 (底线:您没有说明您正在运行什么版本;我无法预测哪种配方最好。) 我的博客避免了配方辩论。

【讨论】:

成功了。 @Rick James,你太棒了!您找到了有关 MyISAM 的正确解决方案。 @CbproAds - 提示 1:如果您有任何其他问题,请提及 MyISAM;回答问题的人会假设 InnoDB 并给出不恰当的答案。提示 2:将引擎更改为 InnoDB。【参考方案2】:

查询看起来很好。

我建议使用以下索引进行优化:

master(expiry_date, userid)
storefront_categories(userid)

第一个索引是master 上子查询的覆盖 索引:这意味着数据库应该能够通过仅查看索引来执行子查询(而仅使用expiry_date在索引中,它仍然需要查看表数据来获取相关的userid)。

第二个索引让数据库优化in操作。

【讨论】:

专线小巴,感谢您的意见。它真的很好用。执行时间从 37 秒降到了 3 秒!但是我想知道为什么 DELETE 语句需要 3 秒,而当我在同一个语句中将 DELETE 替换为 SELECT 时,只用了 0.09 秒。是因为任何 MY.INI 性能调整参数吗? @CbproAds:delete 为数据库带来了更多的工作,所以你所看到的并不让我感到惊讶。 索引 startingexpiry_date 修复了 37 与 4.67 的性能差异。让它“覆盖”会增加额外的推动力。 在真正旧版本的 MySQL 中,IN (SELECT ...) 重新评估了子查询 97K 次!【参考方案3】:

我会尝试exists

DELETE 
FROM storefront_categories 
WHERE EXISTS (SELECT 1 
              FROM MASTER M 
              WHERE M.userid = storefront_categories.userid AND
                    M.expirydate <'2020-02-04'  
              );

索引会在这里,我希望索引在storefront_categories(userid) &amp; MASTER(userid, expirydate)

【讨论】:

【参考方案4】:

我建议您使用带有正确索引的NOT EXISTS

DELETE sc
    FROM storefront_categories sc
    WHERE NOT EXISTS (SELECT 1
                      FROM master m
                      WHERE m.userid = sc.userid AND
                            m.expirydate < '2020-02-04' 
                     );

您想要的索引位于master(userid, expirydate)。列的顺序很重要。对于这个版本,storefront_categories 上的索引没有帮助。

请注意,我更改了日期格式。我建议使用 YYYY-MM-DD 以避免歧义——并使用完整的 10 个字符。

【讨论】:

以上是关于针对 DELETE 查询的 MySQL 性能调整的主要内容,如果未能解决你的问题,请参考以下文章

MySQL性能优化的21个最佳实践

mysql5.7性能调整配置文件

MySQL关于财政年度的查询性能调整

Redshift 中针对多个更新语句的性能调整

Mysql 学习-索引的设计原则

高性能MySQL_第一章-MySQL架构和历史