SQL查询执行时间急剧增加

时间:2017-08-29 14:51:55

标签: php mysql sql

我的问题解释如下。

这是我现在在我的服务器上运行的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);

The execution of the query.

正如您在图片中看到的,在前195个循环中,执行时间介于13到17秒之间。 它在第195次循环时增加到73秒,在第196次循环时增加到1305秒。

现在查询运行了2000秒。

该查询正在删除测试表中没有人正在使用的行。

我将10,000行删除10,000,以使查询快速而不会使服务器过载。

我想知道为什么执行时间会这样增加,我虽然最后会更快,因为我虽然内连接会更快,因为它们在表格中的行数较少。

有没有人有想法?

编辑:表格引擎是MyISAM。

1 个答案:

答案 0 :(得分:0)

根据您的最新评论,内部联接是多余的,因为您要从包含您要加入的值的表格中删除。从本质上讲,您必须两次处理b.id_cdr = a.id_cdr,因为内部联接不会更改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重构以通过限制条款删除订单,这也应该可以提高效果。

虽然我想指出,在此操作期间,MyISAM数据库引擎会执行cdr_transatel_v2上的表锁定。由于MySQL处理并发会话和查询的方式,以这种方式从批量删除中获得的收益并不多,而且实际上只适用于InnoDB和事务。特别是在使用PHP和FastCGI时,而不是Apache mod_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来反转操作会更有利。< / p>

#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;