2017-10-18 7 views
0

サーバー1のテーブルからレコードを選択し、サーバー2に接続して不足しているレコードをそこのクローンテーブルに挿入/無視するために以下のスクリプトを正常にリファクタリングしました。データベースサーバー接続間の挿入/無視のスピードアップ

これは動作しますが、実行には約1.5分かかります。私は成功したが高価なので、誰かが少し速く、より効率的なやり方を手伝ってくれることを願っています。

フェデレーションストレージまたはレプリケーションを行うオプションがないため、これはスクリプトで行う必要があります。以前はソーステーブルの最大IDを使用してこれを行いましたが、挿入後には1日に最大15個のレコードが失われていました。

ここではスクリプトです:

$source_data = mysqli_query($conn, 
    "select * from `cdrdb`.`session` where ts >= now() - INTERVAL 1 DAY"); 

while($source = $source_data->fetch_assoc()) { 
    //Insert new rows into ambition.session 
    $stmt = $conn2->prepare(
     "INSERT IGNORE INTO ambition.session (SESSIONID, 
     SESSIONTYPE,CALLINGPARTYNO,FINALLYCALLEDPARTYNO, 
     DIALPLANNAME,TERMINATIONREASONCODE,ISCLEARINGLEGORIGINATING, 
     CREATIONTIMESTAMP,ALERTINGTIMESTAMP,CONNECTTIMESTAMP,DISCONNECTTIMESTAMP, 
     HOLDTIMESECS,LEGTYPE1,LEGTYPE2,INTERNALPARTYTYPE1,INTERNALPARTYTYPE2 
     ,SERVICETYPEID1,SERVICETYPEID2,EXTENSIONID1,EXTENSIONID2, 
     LOCATION1,LOCATION2,TRUNKGROUPNAME1,TRUNKGROUPNAME2,SESSIONIDTRANSFEREDFROM 
     ,SESSIONIDTRANSFEREDTO,ISTRANSFERINITIATEDBYLEG1, 
     SERVICEEXTENSION1,SERVICEEXTENSION2,SERVICENAME1, 
     SERVICENAME2,MISSEDUSERID2,ISEMERGENCYCALL,NOTABLECALLID, 
     RESPONSIBLEUSEREXTENSIONID, 
     ORIGINALLYCALLEDPARTYNO,ACCOUNTCODE,ACCOUNTCLIENT,ORIGINATINGLEGID 
     ,SYSTEMRESTARTNO,PATTERN,HOLDCOUNT,AUXSESSIONTYPE, 
     DEVICEID1,DEVICEID2,ISLEG1ORIGINATING,ISLEG2ORIGINATING, 
     GLOBALCALLID,CADTEMPLATEID,CADTEMPLATEID2,ts,INITIATOR, 
     ACCOUNTNAME,APPNAME,CALLID,CHRTYPE,CALLERNAME,serviceid1,serviceid2) 

     VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)") 
    or die(mysqli_error($conn2)) ; 

    mysqli_stmt_bind_param($stmt, 
     "iisssiissssiiiiiiiiissssiiissssiiiisssiisiiiiiiiiisisssisii" 
     ,$source['SESSIONID'] 
     ,$source['SESSIONTYPE'] 
     ,$source['CALLINGPARTYNO'] 
     //omitting other columns for sake of space 
     ); 
      $stmt->execute() or die(mysqli_error($conn2)); 
            } 
+0

ループのたびに 'prepare'を呼び出す必要はありません。文を準備し、パラメータを一度バインドしてから、ループを使用してパラメータを更新し、 'execute()'を呼び出します。 – Barmar

+0

おそらくループは必要ありません。 'INSERT IGNORE INTO tablename(columns)SELECT ... from othertable WHERE ... ' – Barmar

+0

残りの' mysqli_stmt_bind_param'呼び出しはどこにありますか? – Barmar

答えて

1

シンプルな改善がループの前にprepare()への呼び出しを移動することです。準備されたステートメントはループごとに同じであるため、毎回DBサーバーに連絡する必要はありません。

ループの外側にあるbind_param()へのコールを移動することもできます。これは、変数が毎回同じであるためです。 bind_paramは参照にバインドされるため、変数を更新すると、​​を呼び出すときに挿入される内容が変更されます。

しかし、これらはほんのわずかな違いを生じる可能性があります。 INSERTクエリの速度を改善する最も効果的な方法の1つは、一度に複数の行を挿入することです。これは、$stmt->execute()の呼び出しでパラメータの配列を提供できるので、mysqliよりもPDOではるかに簡単です。コードは次のようになります。これは私がそれを書いているように動作するために

$params = array(); 
$count = 0; 
$batch_size = 100; 
$placeholders = implode(", ", array_fill(0, $batch_size, "(SESSIONID, 
    SESSIONTYPE,CALLINGPARTYNO,FINALLYCALLEDPARTYNO, 
    DIALPLANNAME,TERMINATIONREASONCODE,ISCLEARINGLEGORIGINATING, 
    CREATIONTIMESTAMP,ALERTINGTIMESTAMP,CONNECTTIMESTAMP,DISCONNECTTIMESTAMP, 
    HOLDTIMESECS,LEGTYPE1,LEGTYPE2,INTERNALPARTYTYPE1,INTERNALPARTYTYPE2 
    ,SERVICETYPEID1,SERVICETYPEID2,EXTENSIONID1,EXTENSIONID2, 
    LOCATION1,LOCATION2,TRUNKGROUPNAME1,TRUNKGROUPNAME2,SESSIONIDTRANSFEREDFROM 
    ,SESSIONIDTRANSFEREDTO,ISTRANSFERINITIATEDBYLEG1, 
    SERVICEEXTENSION1,SERVICEEXTENSION2,SERVICENAME1, 
    SERVICENAME2,MISSEDUSERID2,ISEMERGENCYCALL,NOTABLECALLID, 
    RESPONSIBLEUSEREXTENSIONID, 
    ORIGINALLYCALLEDPARTYNO,ACCOUNTCODE,ACCOUNTCLIENT,ORIGINATINGLEGID 
    ,SYSTEMRESTARTNO,PATTERN,HOLDCOUNT,AUXSESSIONTYPE, 
    DEVICEID1,DEVICEID2,ISLEG1ORIGINATING,ISLEG2ORIGINATING, 
    GLOBALCALLID,CADTEMPLATEID,CADTEMPLATEID2,ts,INITIATOR, 
    ACCOUNTNAME,APPNAME,CALLID,CHRTYPE,CALLERNAME,serviceid1,serviceid2)")); 
$stmt = $pdo->prepare("INSERT INTO ambition.sentence (<columns>) VALUES $placeholders"); 
while ($row = $source_data->fetch(PDO::FETCH_NUM)) { 
    $params += $row; // Append this row to $params 
    $count++; 
    if ($count != $batch_size) { 
     continue; 
    } 
    $stmt->execute($params); 
    // Reset variables for next batch 
    $params = array(); 
    $count = 0; 
} 
if ($count) { // Handle the last batch that isn't the full size 
    $placeholders = implode(", ", array_fill(0, $count, "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")); 
    $stmt = $pdo->prepare("INSERT INTO ambition.sentence (SESSIONID, 
     SESSIONTYPE,CALLINGPARTYNO,FINALLYCALLEDPARTYNO, 
     DIALPLANNAME,TERMINATIONREASONCODE,ISCLEARINGLEGORIGINATING, 
     CREATIONTIMESTAMP,ALERTINGTIMESTAMP,CONNECTTIMESTAMP,DISCONNECTTIMESTAMP, 
     HOLDTIMESECS,LEGTYPE1,LEGTYPE2,INTERNALPARTYTYPE1,INTERNALPARTYTYPE2 
     ,SERVICETYPEID1,SERVICETYPEID2,EXTENSIONID1,EXTENSIONID2, 
     LOCATION1,LOCATION2,TRUNKGROUPNAME1,TRUNKGROUPNAME2,SESSIONIDTRANSFEREDFROM 
     ,SESSIONIDTRANSFEREDTO,ISTRANSFERINITIATEDBYLEG1, 
     SERVICEEXTENSION1,SERVICEEXTENSION2,SERVICENAME1, 
     SERVICENAME2,MISSEDUSERID2,ISEMERGENCYCALL,NOTABLECALLID, 
     RESPONSIBLEUSEREXTENSIONID, 
     ORIGINALLYCALLEDPARTYNO,ACCOUNTCODE,ACCOUNTCLIENT,ORIGINATINGLEGID 
     ,SYSTEMRESTARTNO,PATTERN,HOLDCOUNT,AUXSESSIONTYPE, 
     DEVICEID1,DEVICEID2,ISLEG1ORIGINATING,ISLEG2ORIGINATING, 
     GLOBALCALLID,CADTEMPLATEID,CADTEMPLATEID2,ts,INITIATOR, 
     ACCOUNTNAME,APPNAME,CALLID,CHRTYPE,CALLERNAME,serviceid1,serviceid2) VALUES $placeholders"); 
    $stmt->execute($params); 
} 

、あなたはSELECTクエリによって返された列は、あなたが挿入しているリストと同じ順序であることを確認する必要があります。これを行うときはSELECT *を使用しないでください。ソーステーブルのスキーマに変更があった場合に驚くことはありません。

+0

ありがとうございます。私はこれを試してみるためにこれを挿入しましたが、 '' $ stmt = $ pdo-> prepare''行で未定義の変数$ pdoを得ています –

+0

これは単なるPDOの変数です接続は 'new PDO'によって返されます。 – Barmar

+0

mysqliで使用している '$ conn'変数のようです。 – Barmar

0

おそらく、一度に1行を挿入し、行ごとに1つのトランザクションは、データをロードするための最も遅い方法であることを今では知っています。

は、私がデータをロードするさまざまな方法をテストしている、と私はPerconaライブ2017で講演を行いました:

Load Data Fast!

TL; DR:あなたがにあなたの元データをダンプする必要がある場合でも、使用LOAD DATA INFILEまずCSVファイルを作成します。私はそれをテストしていないのに

はここでは、例です:

$all_columns = " 
    `SESSIONID`, `SESSIONTYPE`, `CALLINGPARTYNO`, `FINALLYCALLEDPARTYNO`, 
    `DIALPLANNAME`, `TERMINATIONREASONCODE`, `ISCLEARINGLEGORIGINATING`, 
    `CREATIONTIMESTAMP`, `ALERTINGTIMESTAMP`, `CONNECTTIMESTAMP`, 
    `DISCONNECTTIMESTAMP`, `HOLDTIMESECS`, `LEGTYPE1`, `LEGTYPE2`, 
    `INTERNALPARTYTYPE1`, `INTERNALPARTYTYPE2`, `SERVICETYPEID1`, 
    `SERVICETYPEID2`, `EXTENSIONID1`, `EXTENSIONID2`, `LOCATION1`, `LOCATION2`, 
    `TRUNKGROUPNAME1`, `TRUNKGROUPNAME2`, `SESSIONIDTRANSFEREDFROM`, 
    `SESSIONIDTRANSFEREDTO`, `ISTRANSFERINITIATEDBYLEG1`, `SERVICEEXTENSION1`, 
    `SERVICEEXTENSION2`, `SERVICENAME1`, `SERVICENAME2`, `MISSEDUSERID2`, 
    `ISEMERGENCYCALL`, `NOTABLECALLID`, `RESPONSIBLEUSEREXTENSIONID`, 
    `ORIGINALLYCALLEDPARTYNO`, `ACCOUNTCODE`, `ACCOUNTCLIENT`, `ORIGINATINGLEGID`, 
    `SYSTEMRESTARTNO`, `PATTERN`, `HOLDCOUNT`, `AUXSESSIONTYPE`, `DEVICEID1`, 
    `DEVICEID2`, `ISLEG1ORIGINATING`, `ISLEG2ORIGINATING`, `GLOBALCALLID`, 
    `CADTEMPLATEID`, `CADTEMPLATEID2`, `ts`, `INITIATOR`, `ACCOUNTNAME`, `APPNAME`, 
    `CALLID`, `CHRTYPE`, `CALLERNAME`, `serviceid1`, `serviceid2`" 

$select_sql = " 
    SELECT $all_columns FROM `cdrdb`.`session` 
    WHERE ts >= NOW() - INTERVAL 1 DAY"; 

$source_data = mysqli_query($conn, $select_sql); 

$tmpfilename = tempnam('/tmp', 'data'); 
fp = fopen($tmpfilename, 'w'); // do proper error handling and use 
while ($source = $source_data->fetch_assoc()) { 
    fputcsv($fp, array_values($source)); 
} 
fclose($fp); 

$tmpfilename = mysqli_real_escape_string($conn2, $tmpfilename); 

$load_sql = " 
    LOAD DATA LOCAL INFILE '$tmpfilename' 
    IGNORE INTO TABLE `ambition`.`session` 
    FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"' 
    ($all_columns)"; 

$conn2->query($load_sql); 
関連する問題