针对 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
为数据库带来了更多的工作,所以你所看到的并不让我感到惊讶。
索引 starting 与 expiry_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) & 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 性能调整的主要内容,如果未能解决你的问题,请参考以下文章