[英]SQL query execution time dramatically increasing
我的问题在下面解释。
这是我现在在服务器上运行的PHP代码:
$limit = 10000;
$annee = '2017';
//Counting the lines I need to delete
$sql = " SELECT COUNT(*) FROM historisation.cdr_".$annee." a
INNER JOIN transatel.cdr_transatel_v2 b ON a.id_cdr = b.id_cdr ";
$t = $db_transatel->selectAll($sql);
//The number of lines I have to delete
$i = $t[0][0];
do {
if ($i < $limit) {
$limit = $i;
}
//The problem is comming from that delete
$selectFromHistoryAndDelete = " DELETE FROM transatel.cdr_transatel_v2
WHERE id_cdr IN (
SELECT a.id_cdr FROM historisation.cdr_".$annee." a
INNER JOIN (SELECT id_cdr FROM historisation.cdr_transatel_v2) b ON a.id_cdr = b.id_cdr
)
LIMIT " . $limit;
$delete = $db_transatel->exec($selectFromHistoryAndDelete, $params);
$i = $i - $limit;
} while ($i > 0);
如您在图片上看到的,在第一个195循环中,执行时间在13到17秒之间。 在第195次循环时增加到73秒,在第196次循环时增加到1305秒。
现在查询运行2000秒。
查询正在删除没有人使用的测试表中的行。
我要删除10,000行乘10,000行,以使查询更快并且不会使服务器超载。
我想知道为什么执行时间会这样增加,尽管最后虽然会更快,但由于内部联接会更快,因为它们在表中的行会更少。
有人有主意吗?
编辑:表引擎是MyISAM。
根据您的最新注释,内部联接是多余的,因为您要从包含要联接的值的表中删除。 本质上,您必须处理b.id_cdr = a.id_cdr
两次,因为内部cdr_2017
不会更改cdr_2017
上比较的值数,而只是查询要删除的值数。
至于增量缓慢的原因,是因为您正在手动执行与SELECT cdr_id FROM cdr_2017 LIMIT 10000 OFFSET x
相同的功能。
也就是说,您的查询必须对cdr_2017
执行全表扫描,以确定要删除的id值。 删除值时,SQL优化器必须在cdr_2017
表中进一步移动以检索值。
导致
DELETE FROM IN(1,2,3,...10000)
DELETE FROM IN(1,2,3,...20000)
...
DELETE FROM IN(1,2,3,...1000000)
假设cdr_id
是增量主键,要解决此问题,您可以使用从cdr_2017
检索的最后一个索引来筛选所选值。 这将更快,因为不再需要全表扫描来验证联接的记录,因为您现在在查询的两侧都利用了索引值。
$sql = " SELECT COUNT(a.cdr_id) FROM historisation.cdr_".$annee." a
INNER JOIN transatel.cdr_transatel_v2 b ON a.id_cdr = b.id_cdr ";
$t = $db_transatel->selectAll($sql);
//The number of lines I have to delete
$i = $t[0][0];
//set starting index
$previous = 0;
do {
if ($i < $limit) {
$limit = $i;
}
$selectFromHistoryAndDelete = 'DELETE d
FROM transatel.cdr_transatel_v2 AS d
JOIN (
SELECT @previous := cdr_id AS cdr_id
FROM historisation.cdr_2017
WHERE cdr_id > ' . $previous . '
ORDER BY cdr_id
LIMIT 10000
) AS a
ON a.cdr_id = d.cdr_id';
$db_transatel->exec($selectFromHistoryAndDelete, $params);
//retrieve last id selected in cdr_2017 to use in next iteration
$v = $db_transatel->selectAll('SELECT @previous'); //prefer fetchColumn
$previous = $v[0][0];
$i = $i - $limit;
} while ($i > 0);
//optionally reclaim table-space
$db_transatel->exec('OPTIMIZE TABLE transatel.cdr_transatel_v2', $params);
您也可以重构以使用cdr_id > $previous AND cdr_id < $last
来删除limit子句的顺序,这也可以提高性能。
尽管我想指出,在此操作过程中,MyISAM数据库引擎对cdr_transatel_v2进行了表锁定。 由于MySQL处理并发会话和查询的方式,以这种方式进行批量删除并没有太大收益,实际上仅适用于InnoDB和事务。 与Apache mod_php相比,尤其是在结合使用FastCGI和PHP的情况下。 由于不在cdr_transatel_v2上的其他查询仍将执行,并且在cdr_transatel_v2上的写操作仍将排队。 如果使用mod_php,我会将限制减少到1,000
条记录,以减少队列时间。 有关更多信息,请参见https://dev.mysql.com/doc/refman/5.7/en/internal-locking.html#internal-table-level-locking
替代方法。
考虑到需要删除的记录数量很多,当删除的记录超过保留的记录时,使用INSERT
而不是DELETE
来反转操作将更为有益。
#ensure the storage table doesn't exist already
DROP TABLE IF EXISTS cdr_transatel_temp;
#duplicate the structure of the original table
CREATE TABLE transatel.cdr_transatel_temp
LIKE transatel.cdr_transatel_v2;
#copy the records that are not to be deleted from the original table
INSERT transatel.cdr_transatel_temp
SELECT *
FROM transatel.cdr_transatel_v2 AS d
LEFT JOIN historisation.cdr_2017 AS b
ON b.cdr_id = d.cdr_id
WHERE b.cdr_id IS NULL;
#replace the original table with the storage table
RENAME TABLE transatel.cdr_transatel_v2 to transatel.backup,
transatel.cdr_transatel_temp to cdr_transatel_v2;
#remove the original table
DROP TABLE transatel.backup;
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.