2017-08-29 24 views
0

私の問題は以下で説明します。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行を削除します。

なぜ私は実行時間がそれほど長くなっているのだろうと思っていますが、テーブル内の行が少ないほど内部結合がはるかに速くなるため、最後は速くなります。

アイデアはありますか?

編集:テーブルエンジンはMyISAMです。

+0

ロールバックセグメント。 DBはどこかの行を削除してロールバックを処理できると思います。各削除の後にコミットを試みてください。 – StanislavL

+0

悲しいことに、私はすでにそれについて考えていますが、テーブルにあるテーブルはMyIASMにあり、ロールバックとコミットをサポートしていません。テーブルが大きい場合、 – McKenneii

+1

INが遅すぎます。 JOINを使用して削除する行を見つける方がはるかに高速です。 – vladatr

答えて

0

あなたが参加している値を含むテーブルから削除しているので、内部の結合は最新のコメントに基づいて冗長です。本質的には、cdr_2017で比較される値の数は、内部結合によって変更されないため、b.id_cdr = a.id_cdrを2回処理する必要があります。削除される値の数だけが変更されます。

増分遅さの原因は、手動でSELECT cdr_id FROM cdr_2017 LIMIT 10000 OFFSET xと同じ機能を実行しているためです。

つまり、削除するID値を決定するために、クエリでフルテーブルスキャンcdr_2017を実行する必要があります。値を削除すると、SQLオプティマイザはcdr_2017テーブルをさらに移動して値を取得する必要があります。 cdr_idは、あなたが選択した値をフィルタリングするcdr_2017から取得した最後のインデックスを使用することがある問題を解決するための増分主キー、であると仮定すると、

DELETE FROM IN(1,2,3,...10000) 
DELETE FROM IN(1,2,3,...20000) 
... 
DELETE FROM IN(1,2,3,...1000000) 

に結果の

。 クエリの両側でインデックス値を利用しているため、結合されたレコードの検証にフルテーブルスキャンが不要になるため、これははるかに高速になります。

$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とトランザクションにのみ適用されます。特に、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


代替アプローチを参照してください。

削除する必要があるレコードの数が多いことを考慮すると、削除されたレコードが削除されたレコードを超えると、DELETEの代わりにINSERTを使用して操作を逆転させると効果的です。

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

それについて考えて答えてくれてありがとう。もし私がそれのためのenougthの時間を見つけることができれば、私は確かに今日の最初の解決策を試みるでしょう。 @VladatrはIN公演について語ったので、私はそれなしで私の質問をしました。削除は〜0.07秒で実行されます。私はあなたの質問がより速いかどうか試してみるでしょう。助けてくれてありがとう! – McKenneii

関連する問題