PDO ที่เตรียมไว้แทรกหลายแถวในแบบสอบถามเดียว


146

ขณะนี้ฉันใช้ SQL ประเภทนี้บน MySQL เพื่อแทรกค่าหลายแถวในแบบสอบถามเดียว:

INSERT INTO `tbl` (`key1`,`key2`) VALUES ('r1v1','r1v2'),('r2v1','r2v2'),...

ในการอ่านใน PDO คำสั่งที่เตรียมไว้ควรให้ความปลอดภัยที่ดีกว่าแบบสอบถามแบบคงที่

ฉันต้องการทราบว่าเป็นไปได้หรือไม่ที่จะสร้าง "การแทรกค่าหลายแถวโดยใช้การค้นหาหนึ่งคำ" โดยใช้คำสั่งที่เตรียมไว้

ถ้าใช่ฉันจะรู้ได้อย่างไรว่าฉันจะใช้มันได้อย่างไร


ระมัดระวังด้วยคำตอบมากมายสำหรับ$stmt->execute($data); php.net/manual/en/…โดยทั่วไปแล้วพารามิเตอร์ทั้งหมดจะผ่านการตรวจสอบความถูกต้องเป็นสตริง เพียงวนรอบข้อมูลหลังจากสร้างแบบสอบถามและด้วยตนเองbindValueหรือbindParamผ่านประเภทเป็นอาร์กิวเมนต์ที่สาม
MrMesees

คำตอบ:


151

หลายค่าแทรกด้วยคำสั่งที่เตรียม PDO

การแทรกค่าหลายค่าในหนึ่งรันคำสั่ง ทำไมเพราะตามหน้านี้มันเร็วกว่าเม็ดมีดทั่วไป

$datafields = array('fielda', 'fieldb', ... );

$data[] = array('fielda' => 'value', 'fieldb' => 'value' ....);
$data[] = array('fielda' => 'value', 'fieldb' => 'value' ....);

ค่าข้อมูลมากขึ้นหรือคุณอาจมีลูปที่เติมข้อมูล

ด้วยส่วนแทรกที่เตรียมไว้คุณจำเป็นต้องรู้เขตข้อมูลที่คุณแทรกและจำนวนเขตข้อมูลที่จะสร้าง? ตัวยึดตำแหน่งเพื่อผูกพารามิเตอร์ของคุณ

insert into table (fielda, fieldb, ... ) values (?,?...), (?,?...)....

นั่นคือพื้นฐานที่ว่าเราต้องการให้คำสั่งแทรกมีลักษณะอย่างไร

ตอนนี้รหัส:

function placeholders($text, $count=0, $separator=","){
    $result = array();
    if($count > 0){
        for($x=0; $x<$count; $x++){
            $result[] = $text;
        }
    }

    return implode($separator, $result);
}

$pdo->beginTransaction(); // also helps speed up your inserts.
$insert_values = array();
foreach($data as $d){
    $question_marks[] = '('  . placeholders('?', sizeof($d)) . ')';
    $insert_values = array_merge($insert_values, array_values($d));
}

$sql = "INSERT INTO table (" . implode(",", $datafields ) . ") VALUES " .
       implode(',', $question_marks);

$stmt = $pdo->prepare ($sql);
try {
    $stmt->execute($insert_values);
} catch (PDOException $e){
    echo $e->getMessage();
}
$pdo->commit();

แม้ว่าในการทดสอบของฉันมีความแตกต่างเพียง 1 วินาทีเมื่อใช้หลายเม็ดมีดและเม็ดมีดทั่วไปที่เตรียมค่าเดียว


4
ตัวพิมพ์ใหญ่ในคำอธิบายข้างต้นกล่าวถึง $ datafield แม้ว่า $ datafield จะถูกใช้ใน $ sql ดังนั้นการคัดลอกวางจะทำให้เกิดข้อผิดพลาด โปรดแก้ไข ขอบคุณสำหรับวิธีแก้ปัญหานี้
pal4life

1
ใช้สิ่งนี้เป็นระยะเวลาหนึ่งแล้วสังเกตว่าค่าที่มีเครื่องหมายคำพูดเดี่ยวอยู่ในนั้นจะไม่สามารถหลีกหนีได้อย่างถูกต้อง การใช้เครื่องหมายอัญประกาศคู่บน implosion ทำงานได้อย่างมีเสน่ห์สำหรับฉัน: $ a [] = '("'. implode (", ", $ question_marks). '", NOW ())';
qwertzman

1
array_merge ดูเหมือนว่าจะมีราคาแพงกว่าการใช้ array_push
K2xL

14
เมื่อคุณพูดว่า "มีความแตกต่างเพียง 1 วินาที" คุณใส่ข้อมูลลงไปกี่แถว? 1 วินาทีค่อนข้างสำคัญขึ้นอยู่กับบริบท
Kevin Dice

3
การเพิ่มประสิทธิภาพ: ไม่มีจุดในการโทรplaceholders()ซ้ำแล้วซ้ำอีก เรียกมันหนึ่งครั้งก่อนที่ลูปด้วยsizeof($datafields)และผนวกสตริงผลลัพธ์$question_marks[]เข้าไปในลูป
พัฒนา AVID

71

คำตอบเดียวกับ Mr. Balagtas ชัดเจนขึ้นเล็กน้อย ...

รุ่นล่าสุด MySQL และ PHP PDO ทำสนับสนุนหลายแถวINSERTงบ

ภาพรวม SQL

สร้าง SQL จะมีลักษณะบางอย่างเช่นนี้สมมติว่าตารางที่ 3 คอลัมน์ที่คุณต้องการINSERTที่จะ

INSERT INTO tbl_name
            (colA, colB, colC)
     VALUES (?, ?, ?), (?, ?, ?), (?, ?, ?) [,...]

ON DUPLICATE KEY UPDATEทำงานตามที่คาดไว้แม้จะมี INSERT หลายแถว ผนวกสิ่งนี้:

ON DUPLICATE KEY UPDATE colA = VALUES(colA), colB = VALUES(colB), colC = VALUES(colC)

ภาพรวม PHP

รหัส PHP ของคุณจะเป็นไปตามปกติ$pdo->prepare($qry)และการ$stmt->execute($params)เรียก PDO

$paramsจะเป็นอาร์เรย์ 1 มิติของทุกINSERTค่าที่จะส่งผ่านไปยัง

ในตัวอย่างข้างต้นควรมี 9 องค์ประกอบ PDO จะใช้ทุกชุด 3 เป็นค่าแถวเดียว (การแทรกแถว 3 แถว 3 คอลัมน์แต่ละแถว = 9 องค์ประกอบอาร์เรย์)

การดำเนินงาน

โค้ดด้านล่างเขียนขึ้นเพื่อความชัดเจนไม่ใช่ประสิทธิภาพ ทำงานกับarray_*()ฟังก์ชั่นPHP เพื่อหาวิธีที่ดีกว่าในการแมปหรือดูข้อมูลของคุณหากคุณต้องการ คุณสามารถใช้ธุรกรรมได้หรือไม่นั้นขึ้นอยู่กับประเภทตาราง MySQL ของคุณ

สมมติว่า:

  • $tblName - ชื่อสตริงของตารางเป็น INSERT ถึง
  • $colNames- อาร์เรย์ 1 มิติของชื่อคอลัมน์ของตารางชื่อคอลัมน์เหล่านี้ต้องเป็นตัวระบุคอลัมน์ MySQL ที่ถูกต้อง หลบหนีพวกเขาด้วย backticks (``) หากพวกเขาไม่
  • $dataVals - อาร์เรย์ mutli มิติที่แต่ละองค์ประกอบเป็นอาร์เรย์ 1 มิติของแถวของค่าเป็น INSERT

รหัสตัวอย่าง

// setup data values for PDO
// memory warning: this is creating a copy all of $dataVals
$dataToInsert = array();

foreach ($dataVals as $row => $data) {
    foreach($data as $val) {
        $dataToInsert[] = $val;
    }
}

// (optional) setup the ON DUPLICATE column names
$updateCols = array();

foreach ($colNames as $curCol) {
    $updateCols[] = $curCol . " = VALUES($curCol)";
}

$onDup = implode(', ', $updateCols);

// setup the placeholders - a fancy way to make the long "(?, ?, ?)..." string
$rowPlaces = '(' . implode(', ', array_fill(0, count($colNames), '?')) . ')';
$allPlaces = implode(', ', array_fill(0, count($dataVals), $rowPlaces));

$sql = "INSERT INTO $tblName (" . implode(', ', $colNames) . 
    ") VALUES " . $allPlaces . " ON DUPLICATE KEY UPDATE $onDup";

// and then the PHP PDO boilerplate
$stmt = $pdo->prepare ($sql);

try {
   $stmt->execute($dataToInsert);
} catch (PDOException $e){
   echo $e->getMessage();
}

$pdo->commit();

6
มันแย่มาก ๆ ที่ PDO จัดการมันด้วยวิธีนี้มีวิธีการที่ยอดเยี่ยมในการทำเช่นนี้ในไดรเวอร์ DB อื่น ๆ
Jonathon

การตั้งค่าตัวยึดนี้ยิ่งเข้มงวดมากขึ้นทำให้$rowPlacesไม่จำเป็นอีกต่อไป:$allPlaces = implode(',', array_fill(0, count($dataVals), '('.str_pad('', (count($colNames)*2)-1, '?,').')'));
ฟิล

ทำงานได้สมบูรณ์แบบ ฉันจะเพิ่มคำตอบนี้เพื่อให้แน่ใจว่ามีความไม่ซ้ำกันของดัชนี (รวมกัน) ในตาราง เช่นเดียวกับในเปลี่ยนแปลงตารางvotesใส่ UNIQUE unique_index( user, email, address);
Giuseppe

1
! น่ากลัว BTW การใช้array_push($dataToInsert, ...array_values($dataVals));จะเร็วขึ้นมากแล้วforeach ($dataVals as $row => $data) {}
Anis

39

สำหรับสิ่งที่คุ้มค่าฉันได้เห็นผู้ใช้จำนวนมากแนะนำให้วนซ้ำคำสั่ง INSERT แทนที่จะสร้างเป็นเคียวรีสตริงเดี่ยวตามคำตอบที่เลือก ฉันตัดสินใจรันการทดสอบอย่างง่ายโดยมีเพียงสองฟิลด์และคำสั่งแทรกขั้นพื้นฐานมาก:

<?php
require('conn.php');

$fname = 'J';
$lname = 'M';

$time_start = microtime(true);
$stmt = $db->prepare('INSERT INTO table (FirstName, LastName) VALUES (:fname, :lname)');

for($i = 1; $i <= 10; $i++ )  {
    $stmt->bindParam(':fname', $fname);
    $stmt->bindParam(':lname', $lname);
    $stmt->execute();

    $fname .= 'O';
    $lname .= 'A';
}


$time_end = microtime(true);
$time = $time_end - $time_start;

echo "Completed in ". $time ." seconds <hr>";

$fname2 = 'J';
$lname2 = 'M';

$time_start2 = microtime(true);
$qry = 'INSERT INTO table (FirstName, LastName) VALUES ';
$qry .= "(?,?), ";
$qry .= "(?,?), ";
$qry .= "(?,?), ";
$qry .= "(?,?), ";
$qry .= "(?,?), ";
$qry .= "(?,?), ";
$qry .= "(?,?), ";
$qry .= "(?,?), ";
$qry .= "(?,?), ";
$qry .= "(?,?)";

$stmt2 = $db->prepare($qry);
$values = array();

for($j = 1; $j<=10; $j++) {
    $values2 = array($fname2, $lname2);
    $values = array_merge($values,$values2);

    $fname2 .= 'O';
    $lname2 .= 'A';
}

$stmt2->execute($values);

$time_end2 = microtime(true);
$time2 = $time_end2 - $time_start2;

echo "Completed in ". $time2 ." seconds <hr>";
?>

ในขณะที่แบบสอบถามโดยรวมนั้นใช้เวลาเป็นมิลลิวินาทีหรือน้อยกว่าแบบสอบถาม (สตริงเดี่ยว) จะเร็วกว่าหรือมากกว่า 8 เท่าอย่างสม่ำเสมอ หากสิ่งนี้ถูกสร้างขึ้นเพื่อบอกว่าสะท้อนการนำเข้าหลายพันแถวในคอลัมน์อื่น ๆ อีกมากมายความแตกต่างอาจมหาศาล


@ JM4 - ความคิดที่ดีในการวาง 10 แถวโดยตรงในการดำเนินการครั้งเดียว แต่ฉันจะแทรกหลายพันแถวเมื่อเก็บไว้ในวัตถุเช่น JSON ได้อย่างไร รหัสของฉันด้านล่างทำงานเป็นระยะ ๆ แต่ฉันจะปรับมันเพื่อแทรก 10 แถวในหนึ่งการดำเนินการได้อย่างไร `foreach ($ json_content เป็น $ datarow) {$ id = $ datarow [id]; $ date = $ datarow [date]; $ row3 = $ datarow [row3]; $ row4 = $ datarow [row4]; $ row5 = $ datarow [row5]; $ row6 = $ datarow [row6]; $ row7 = $ datarow [row7]; // ตอนนี้รัน $ databaseinsert-> execute (); } // end foreach `
ปีเตอร์

@ JM4 - ... และคำถามที่สองของฉันคือ: "ทำไมไม่มีbind_paramคำสั่งในชุดคำสั่งการนำเข้าที่สอง"?
ปีเตอร์

คุณไม่ต้องวนซ้ำสองครั้งใช่ไหม คุณจะต้องสร้างแบบไดนามิก(?,?)ใช่ไหม?
NoobishPro

@NoobishPro ใช่คุณสามารถใช้ / foreach เดียวกันเพื่อสร้างทั้งสองอย่าง
Chazy Chaz

34

คำตอบที่ได้รับการยอมรับโดย Herbert Balagtas ทำงานได้ดีเมื่ออาร์เรย์ข้อมูล $ มีขนาดเล็ก ด้วยอาร์เรย์ข้อมูลขนาดใหญ่ $ ฟังก์ชั่น array_merge จะช้าลงอย่างไม่อนุญาต ไฟล์ทดสอบของฉันเพื่อสร้างอาร์เรย์ข้อมูล $ มี 28 คอลัมน์และประมาณ 80,000 บรรทัด สคริปต์สุดท้ายใช้เวลา41 วินาทีเพื่อให้สมบูรณ์

ใช้array_push ()เพื่อสร้าง $ insert_values แทน array_merge () ส่งผลให้ความเร็ว 100X ขึ้นด้วยเวลาการดำเนินการของ0.41s

Array_merge ที่เป็นปัญหา ():

$insert_values = array();

foreach($data as $d){
 $question_marks[] = '('  . placeholders('?', sizeof($d)) . ')';
 $insert_values = array_merge($insert_values, array_values($d));
}

ในการกำจัดความต้องการ array_merge () คุณสามารถสร้างสองอาร์เรย์ต่อไปนี้แทน:

//Note that these fields are empty, but the field count should match the fields in $datafields.
$data[] = array('','','','',... n ); 

//getting rid of array_merge()
array_push($insert_values, $value1, $value2, $value3 ... n ); 

อาร์เรย์เหล่านี้สามารถใช้งานได้ดังนี้:

function placeholders($text, $count=0, $separator=","){
    $result = array();
    if($count > 0){
        for($x=0; $x<$count; $x++){
            $result[] = $text;
        }
    }

    return implode($separator, $result);
}

$pdo->beginTransaction();

foreach($data as $d){
 $question_marks[] = '('  . placeholders('?', sizeof($d)) . ')';
}

$sql = "INSERT INTO table (" . implode(",", array_keys($datafield) ) . ") VALUES " . implode(',', $question_marks);

$stmt = $pdo->prepare ($sql);
try {
    $stmt->execute($insert_values);
} catch (PDOException $e){
    echo $e->getMessage();
}
$pdo->commit();

4
ใน PHP 5.6 คุณสามารถทำแทนarray_push($data, ...array_values($row)) $data = array_merge($data, array_values($row));เร็วขึ้นมาก
mpen

ทำไม 5.6 เอกสารไม่ได้พูดอะไรเกี่ยวกับ 5.6 แต่array_push()สามารถใช้ได้แม้ใน php 4
ZurabWeb

1
@Piero เป็นโค้ด PHP 5.6+ เท่านั้นไม่ใช่เพราะการใช้งานarray_push()แต่เนื่องจาก @Mark ใช้การคลายอาร์กิวเมนต์ สังเกตเห็นการ...array_values()โทรที่นั่น?
mariano.iglesias

@ mariano.iglesias array_values()เป็นเช่นเดียวกับที่มีอยู่ใน PHP 4. argument unpackingถ้าไม่แน่ใจว่านั่นคือสิ่งที่คุณหมายถึงโดย
ZurabWeb

2
@Piero การเปิดกล่องโต้แย้งเป็นคุณลักษณะที่แนะนำใน PHP 5.6 มันเป็นวิธีที่จะให้ข้อโต้แย้งหลาย ๆ แบบเป็นอาร์เรย์ ตรวจสอบที่นี่ - php.net/manual/en/…
Anis

14

สองแนวทางที่เป็นไปได้:

$stmt = $pdo->prepare('INSERT INTO foo VALUES(:v1_1, :v1_2, :v1_3),
    (:v2_1, :v2_2, :v2_3),
    (:v2_1, :v2_2, :v2_3)');
$stmt->bindValue(':v1_1', $data[0][0]);
$stmt->bindValue(':v1_2', $data[0][1]);
$stmt->bindValue(':v1_3', $data[0][2]);
// etc...
$stmt->execute();

หรือ:

$stmt = $pdo->prepare('INSERT INTO foo VALUES(:a, :b, :c)');
foreach($data as $item)
{
    $stmt->bindValue(':a', $item[0]);
    $stmt->bindValue(':b', $item[1]);
    $stmt->bindValue(':c', $item[2]);
    $stmt->execute();
}

หากข้อมูลสำหรับแถวทั้งหมดอยู่ในอาร์เรย์เดียวฉันจะใช้วิธีแก้ปัญหาที่สอง


10
ในตอนหลังคุณไม่ได้ทำการโทรหลายครั้ง (อาจเป็นหลายพัน) แยกกันแทนที่จะรวมเป็นหนึ่งคำสั่ง?
JM4

@ JM4 คุณแนะนำว่า$stmt->execute();ควรอยู่นอกวง foreach หรือไม่
bafromca

@bafromca - ใช่ฉันเป็น ดูคำตอบของฉันด้านบนพร้อม upvotes ในคำสั่งแทรกบริสุทธิ์ไม่มีเหตุผลที่ฉันสามารถเกิดขึ้นกับเหตุผลว่ามันไม่สามารถเป็นคำสั่งเดียว หนึ่งสายหนึ่งดำเนินการ ในความเป็นจริงคำตอบของฉันตั้งแต่ต้นปี 2555 อาจจะดีขึ้นอีก - บางสิ่งที่ฉันจะทำต่อไปเมื่อฉันมีเวลามากขึ้น หากคุณเริ่มการขว้างปาในชุดค่าผสมแทรก / อัปเดต / ลบนั่นเป็นเรื่องที่แตกต่าง
JM4

12

นั่นไม่ใช่วิธีที่คุณใช้ข้อความที่เตรียมไว้

การแทรกหนึ่งแถวต่อคิวรีอย่างสมบูรณ์นั้นไม่เป็นไรเพราะคุณสามารถดำเนินการคำสั่งที่เตรียมไว้ได้หลายครั้งด้วยพารามิเตอร์ที่แตกต่างกัน ในความเป็นจริงนั้นเป็นข้อดีอย่างหนึ่งที่ยิ่งใหญ่เพราะช่วยให้คุณสามารถแทรกแถวจำนวนมากในลักษณะที่มีประสิทธิภาพปลอดภัยและสะดวกสบาย

ดังนั้นอาจเป็นไปได้ที่จะใช้รูปแบบที่คุณเสนออย่างน้อยก็สำหรับจำนวนแถวที่แน่นอน แต่เกือบจะรับประกันได้ว่านี่ไม่ใช่สิ่งที่คุณต้องการจริงๆ


1
คุณช่วยแนะนำวิธีที่ดีกว่าในการแทรกหลายแถวลงในตารางได้หรือไม่?
Crashthatch

@Crashthatch: เพียงทำแบบไร้เดียงสา: ตั้งค่าคำสั่งที่เตรียมไว้หนึ่งครั้งจากนั้นเรียกใช้งานสำหรับแต่ละแถวที่มีค่าต่างกันสำหรับพารามิเตอร์ที่ถูกผูกไว้ นั่นเป็นวิธีที่สองในคำตอบของ Zyk
sebasgo

2
วัตถุประสงค์ที่คุณกล่าวถึงเพื่อจัดทำแถลงการณ์นั้นถูกต้อง แต่การใช้ multi -insert เป็นอีกเทคนิคหนึ่งในการปรับปรุงความเร็วของเม็ดมีดและสามารถใช้กับคำสั่งที่เตรียมไว้ได้เช่นกัน จากประสบการณ์ของฉันในขณะที่ย้ายข้อมูล 30 ล้านแถวโดยใช้คำสั่งที่เตรียม PDO ฉันเห็นว่าการแทรกหลายครั้งเร็วกว่า 7-10 เท่าจากนั้นจัดกลุ่มการแทรกเดี่ยวในการทำธุรกรรม
Anis

1
เห็นด้วยแน่นอนกับ Anis ฉันมี 100k แถวและเพิ่มความเร็วมากขึ้นด้วยการแทรกแถว muli
Kenneth

การอ้างว่าการเรียกฐานข้อมูลเชิงสัมพันธ์ในลูปหนึ่งครั้งต่อแถวนั้นเป็นสิ่งที่ดีคือสิ่งที่ฉันไม่เห็นด้วย ลงคะแนนให้ จริงอยู่ที่บางครั้งก็โอเค ฉันไม่เชื่อในความสมบูรณ์แบบด้วยวิศวกรรม แต่นี่เป็นรูปแบบการต่อต้านที่ควรใช้ในบางกรณีเท่านั้น
Brandon

8

คำตอบที่สั้นกว่า: ทำให้อาร์เรย์ของข้อมูลเรียงตามลำดับจากคอลัมน์

//$array = array( '1','2','3','4','5', '1','2','3','4','5');
$arCount = count($array);
$rCount = ($arCount  ? $arCount - 1 : 0);
$criteria = sprintf("(?,?,?,?,?)%s", str_repeat(",(?,?,?,?,?)", $rCount));
$sql = "INSERT INTO table(c1,c2,c3,c4,c5) VALUES$criteria";

เมื่อแทรก 1,000 เรคคอร์ดหรือมากกว่านั้นคุณไม่ต้องการวนซ้ำทุกเรคคอร์ดเพื่อแทรกเมื่อทุกสิ่งที่คุณต้องการคือการนับค่า


5

นี่คือวิธีการง่ายๆของฉัน

    $values = array();
    foreach($workouts_id as $value){
      $_value = "(".$value.",".$plan_id.")";
      array_push($values,$_value);
    }
    $values_ = implode(",",$values);

    $sql = "INSERT INTO plan_days(id,name) VALUES" . $values_."";
    $stmt = $this->conn->prepare($sql);
    $stmt->execute();

6
คุณกำลังเอาชนะจุดของการใช้งบเตรียม op กังวลเกี่ยวกับความปลอดภัยในคำถามOn the readings on PDO, the use prepared statements should give me a better security than static queries.
YesItsMe

2
เพียงแค่ภาพที่คุณไม่ผ่านการตรวจสอบ$workouts_idซึ่งสามารถมี$valueข้อมูลที่ไม่คาดคิด คุณไม่สามารถรับประกันได้ว่าอาจไม่ใช่ตอนนี้ แต่ในอนาคตนักพัฒนารายอื่นจะทำให้ข้อมูลนี้ไม่ปลอดภัย ดังนั้นฉันคิดว่าค่อนข้างถูกต้องมากขึ้นที่จะทำให้แบบสอบถามจัดทำโดย PDO
Nikita_kharkov_ua

3

นี่คือคลาสที่ฉันเขียนทำแทรกหลายตัวเลือกที่มีการล้าง:

<?php

/**
 * $pdo->beginTransaction();
 * $pmi = new PDOMultiLineInserter($pdo, "foo", array("a","b","c","e"), 10);
 * $pmi->insertRow($data);
 * ....
 * $pmi->insertRow($data);
 * $pmi->purgeRemainingInserts();
 * $pdo->commit();
 *
 */
class PDOMultiLineInserter {
    private $_purgeAtCount;
    private $_bigInsertQuery, $_singleInsertQuery;
    private $_currentlyInsertingRows  = array();
    private $_currentlyInsertingCount = 0;
    private $_numberOfFields;
    private $_error;
    private $_insertCount = 0;

    function __construct(\PDO $pdo, $tableName, $fieldsAsArray, $bigInsertCount = 100) {
        $this->_numberOfFields = count($fieldsAsArray);
        $insertIntoPortion = "INSERT INTO `$tableName` (`".implode("`,`", $fieldsAsArray)."`) VALUES";
        $questionMarks  = " (?".str_repeat(",?", $this->_numberOfFields - 1).")";

        $this->_purgeAtCount = $bigInsertCount;
        $this->_bigInsertQuery    = $pdo->prepare($insertIntoPortion.$questionMarks.str_repeat(", ".$questionMarks, $bigInsertCount - 1));
        $this->_singleInsertQuery = $pdo->prepare($insertIntoPortion.$questionMarks);
    }

    function insertRow($rowData) {
        // @todo Compare speed
        // $this->_currentlyInsertingRows = array_merge($this->_currentlyInsertingRows, $rowData);
        foreach($rowData as $v) array_push($this->_currentlyInsertingRows, $v);
        //
        if (++$this->_currentlyInsertingCount == $this->_purgeAtCount) {
            if ($this->_bigInsertQuery->execute($this->_currentlyInsertingRows) === FALSE) {
                $this->_error = "Failed to perform a multi-insert (after {$this->_insertCount} inserts), the following errors occurred:".implode('<br/>', $this->_bigInsertQuery->errorInfo());
                return false;
            }
            $this->_insertCount++;

            $this->_currentlyInsertingCount = 0;
            $this->_currentlyInsertingRows = array();
        }
        return true;
    }

    function purgeRemainingInserts() {
        while ($this->_currentlyInsertingCount > 0) {
            $singleInsertData = array();
            // @todo Compare speed - http://www.evardsson.com/blog/2010/02/05/comparing-php-array_shift-to-array_pop/
            // for ($i = 0; $i < $this->_numberOfFields; $i++) $singleInsertData[] = array_pop($this->_currentlyInsertingRows); array_reverse($singleInsertData);
            for ($i = 0; $i < $this->_numberOfFields; $i++) array_unshift($singleInsertData, array_pop($this->_currentlyInsertingRows));

            if ($this->_singleInsertQuery->execute($singleInsertData) === FALSE) {
                $this->_error = "Failed to perform a small-insert (whilst purging the remaining rows; the following errors occurred:".implode('<br/>', $this->_singleInsertQuery->errorInfo());
                return false;
            }
            $this->_currentlyInsertingCount--;
        }
    }

    public function getError() {
        return $this->_error;
    }
}

สวัสดีปีแอร์ บางทีคุณอาจไม่ได้อยู่ที่นี่อีกแล้ว อย่างไรก็ตามฉันแค่อยากจะชี้ให้เห็นว่าความคิดของฉันสำหรับปัญหานี้ดูเกือบจะเหมือนกับของคุณ เป็นเรื่องบังเอิญแท้ๆอย่างที่ฉันคิดว่ามันไม่มีอะไรมากไปกว่านี้อีกแล้ว ฉันเพิ่มคลาสสำหรับ DELETE- และ UPDATE-Operations เช่นกันและเกี่ยวข้องกับแนวคิดบางอย่างจากที่นี่หลังจากนั้น ฉันไม่เห็นชั้นเรียนของคุณ โปรดแก้ตัวการโปรโมตตัวเองที่ไร้ยางอายของฉันที่นี่ แต่ฉันคิดว่ามันจะเป็นประโยชน์สำหรับใครบางคน หวังว่านี่จะไม่ขัดกับกฎของ SO พบว่ามันนี่
JackLeEmmerdeur

1

นี่คือวิธีที่ฉันทำ:

ขั้นแรกให้กำหนดชื่อคอลัมน์ที่คุณจะใช้หรือเว้นว่างไว้และ pdo จะถือว่าคุณต้องการใช้คอลัมน์ทั้งหมดในตารางซึ่งในกรณีนี้คุณจะต้องแจ้งค่าแถวตามลำดับที่ปรากฏในตาราง .

$cols = 'name', 'middleName', 'eMail';
$table = 'people';

ทีนี้สมมติว่าคุณมีอาร์เรย์สองมิติที่เตรียมไว้แล้ว ทำซ้ำและสร้างสตริงด้วยค่าแถวของคุณเช่น:

foreach ( $people as $person ) {
if(! $rowVals ) {
$rows = '(' . "'$name'" . ',' . "'$middleName'" . ',' .           "'$eMail'" . ')';
} else { $rowVals  = '(' . "'$name'" . ',' . "'$middleName'" . ',' . "'$eMail'" . ')';
}

ตอนนี้สิ่งที่คุณเพิ่งทำคือตรวจสอบว่ามีการกำหนด $ rows ไว้แล้วและหากไม่ใช่ให้สร้างและเก็บค่าแถวและไวยากรณ์ SQL ที่จำเป็นเพื่อให้เป็นคำสั่งที่ถูกต้อง โปรดทราบว่าสตริงควรอยู่ในเครื่องหมายคำพูดคู่และคำพูดเดี่ยวดังนั้นพวกเขาจะได้รับการยอมรับโดยทันทีเช่นนี้

สิ่งที่เหลืออยู่ให้ทำคือเตรียมคำสั่งและดำเนินการเช่น:

$stmt = $db->prepare ( "INSERT INTO $table $cols VALUES $rowVals" );
$stmt->execute ();

ผ่านการทดสอบสูงสุดถึง 2,000 แถวและเวลาดำเนินการไม่เป็นที่พอใจ จะเรียกใช้การทดสอบเพิ่มเติมและจะกลับมาที่นี่ในกรณีที่ฉันมีบางอย่างเพิ่มเติมเพื่อสนับสนุน

ความนับถือ.


1

เนื่องจากยังไม่ได้รับการแนะนำฉันค่อนข้างมั่นใจว่า LOAD DATA INFILE ยังคงเป็นวิธีที่เร็วที่สุดในการโหลดข้อมูลเนื่องจากปิดใช้งานการทำดัชนีแทรกข้อมูลทั้งหมดแล้วจึงเปิดใช้งานดัชนีอีกครั้งในคำขอเดียว

การบันทึกข้อมูลในรูปแบบ csv ควรคำนึงถึงเป็นเรื่องเล็กน้อย fputcsv MyISAM เร็วที่สุด แต่คุณยังคงได้รับประสิทธิภาพที่ยอดเยี่ยมใน InnoDB มีข้อเสียอื่น ๆ ดังนั้นฉันจะไปเส้นทางนี้ถ้าคุณกำลังแทรกข้อมูลจำนวนมากและไม่รบกวนต่ำกว่า 100 แถว


1

แม้ว่าคำถามเก่า ๆ การมีส่วนร่วมทั้งหมดช่วยฉันได้มากดังนั้นนี่คือทางออกของฉันซึ่งใช้ได้ในDbContextชั้นเรียนของฉันเอง $rowsพารามิเตอร์เป็นเพียงอาร์เรย์ของ arrays field name => insert valueสมาคมตัวแทนแถวหรือรุ่นนี้:

หากคุณใช้รูปแบบที่ใช้แบบจำลองสิ่งนี้เหมาะสมอย่างยิ่งเมื่อผ่านข้อมูลแบบจำลองเป็นอาร์เรย์ให้พูดจากToRowArrayวิธีการภายในคลาสรุ่น

หมายเหตุ : ควรไปโดยไม่บอก แต่ไม่อนุญาตให้มีการขัดแย้งกับวิธีนี้เพื่อเปิดเผยต่อผู้ใช้หรือพึ่งพาการป้อนข้อมูลของผู้ใช้ใด ๆ นอกเหนือจากค่าแทรกซึ่งได้รับการตรวจสอบและผ่านการฆ่าเชื้อแล้ว $tableNameโต้แย้งและชื่อคอลัมน์ที่ควรจะกำหนดโดยตรรกะโทร; ตัวอย่างเช่นUserโมเดลสามารถแม็พกับตารางผู้ใช้ซึ่งมีรายการคอลัมน์ที่แม็พกับฟิลด์สมาชิกของโมเดล

public function InsertRange($tableName, $rows)
{
    // Get column list
    $columnList = array_keys($rows[0]);
    $numColumns = count($columnList);
    $columnListString = implode(",", $columnList);

    // Generate pdo param placeholders
    $placeHolders = array();

    foreach($rows as $row)
    {
        $temp = array();

        for($i = 0; $i < count($row); $i++)
            $temp[] = "?";

        $placeHolders[] = "(" . implode(",", $temp) . ")";
    }

    $placeHolders = implode(",", $placeHolders);

    // Construct the query
    $sql = "insert into $tableName ($columnListString) values $placeHolders";
    $stmt = $this->pdo->prepare($sql);

    $j = 1;
    foreach($rows as $row)
    {
        for($i = 0; $i < $numColumns; $i++)
        {
            $stmt->bindParam($j, $row[$columnList[$i]]);
            $j++;
        }
    }

    $stmt->execute();
}

กำจัดทรานแซคชันเนื่องจากไม่มีเหตุผลที่จะใช้หนึ่งรายการสำหรับเคียวรีเดียว และตามปกติรหัสนี้มีความเสี่ยงต่อการฉีด SQL หรือข้อผิดพลาดแบบสอบถาม
สามัญสำนึกของคุณ

คุณพูดถูกเกี่ยวกับการใช้งานธุรกรรมที่ซ้ำซ้อนในกรณีนี้ แต่ฉันไม่เห็นว่าสิ่งนี้มีความเสี่ยงต่อการฉีด SQL มันมีการกำหนดพารามิเตอร์ดังนั้นฉันสามารถสันนิษฐานได้ว่าคุณกำลังสมมติว่า$tableNameมีการเปิดเผยต่อผู้ใช้ซึ่งไม่ใช่ใน DAL คุณสามารถขยายการเรียกร้องของคุณ? การพูดสิ่งต่าง ๆ ไม่เป็นประโยชน์
ลี

ไม่ใช่แค่ชื่อตารางเท่านั้น แต่คุณจะรู้ได้อย่างไรว่าใครจะใช้รหัสที่คุณโพสต์ไว้ที่นี่
สามัญสำนึกของคุณ

ดังนั้นมันเป็นความรับผิดชอบของผู้โพสต์ที่จะอธิบายถึงการใช้รหัสหรือแหล่งที่มาของข้อโต้แย้งทั้งหมด? บางทีฉันอาจมีคนคาดหวังสูงกว่า มันจะทำให้คุณมีความสุขมากขึ้นถ้าฉันเพิ่มบันทึกย่อที่จะไม่อนุญาตให้ผู้ใช้สามารถเข้าถึงได้$tableNameหรือไม่?
ลี

มันเป็นความรับผิดชอบของผู้โพสต์ในการโพสต์รหัสที่เชื่อถือได้หากเจตนาของพวกเขาคือการช่วยเหลือใครสักคนไม่ใช่แค่แสดงออกมา
สามัญสำนึกของคุณ

1

นี่คือวิธีแก้ปัญหาอื่น (แบบบาง) สำหรับปัญหานี้:

ในตอนแรกคุณต้องนับข้อมูลของอาร์เรย์ต้นทาง (ที่นี่: $ aData) ด้วย count () จากนั้นคุณใช้ array_fill () และสร้างอาร์เรย์ใหม่ที่มีรายการมากเท่าที่อาร์เรย์ต้นทางมีแต่ละรายการมีค่า "(?,?)" (จำนวนตัวยึดตำแหน่งขึ้นอยู่กับเขตข้อมูลที่คุณใช้ที่นี่: 2) จากนั้นอาเรย์ที่สร้างขึ้นจะต้องถูกนำไปฝังและใช้กาวคอมม่า ภายในลูป foreach คุณจะต้องสร้างดัชนีอื่นที่เกี่ยวข้องกับจำนวนตัวยึดตำแหน่งที่คุณใช้ (จำนวนตัวยึด * ดัชนีอาร์เรย์ปัจจุบัน + 1) คุณต้องเพิ่ม 1 เข้ากับดัชนีที่สร้างขึ้นหลังจากแต่ละค่าที่ถูกผูกไว้

$do = $db->prepare("INSERT INTO table (id, name) VALUES ".implode(',', array_fill(0, count($aData), '(?,?)')));

foreach($aData as $iIndex => $aValues){
 $iRealIndex = 2 * $iIndex + 1;
 $do->bindValue($iRealIndex, $aValues['id'], PDO::PARAM_INT);
 $iRealIndex = $iRealIndex + 1;
 $do->bindValue($iRealIndex, $aValues['name'], PDO::PARAM_STR);
}

$do->execute();

0

คุณสามารถแทรกหลายแถวในแบบสอบถามเดียวด้วยฟังก์ชันนี้:

function insertMultiple($query,$rows) {
    if (count($rows)>0) {
        $args = array_fill(0, count($rows[0]), '?');

        $params = array();
        foreach($rows as $row)
        {
            $values[] = "(".implode(',', $args).")";
            foreach($row as $value)
            {
                $params[] = $value;
            }
        }

        $query = $query." VALUES ".implode(',', $values);
        $stmt = $PDO->prepare($query);
        $stmt->execute($params);
    }
}

$ แถวเป็นอาร์เรย์ของอาร์เรย์ของค่า ในกรณีของคุณคุณจะเรียกใช้ฟังก์ชันด้วย

insertMultiple("INSERT INTO tbl (`key1`,`key2`)",array(array('r1v1','r1v2'),array('r2v1','r2v2')));

สิ่งนี้มีประโยชน์ที่คุณใช้ในงบที่เตรียมไว้ขณะที่แทรกหลายแถวด้วยแบบสอบถามเดียว การรักษาความปลอดภัย!


0

นี่คือวิธีแก้ปัญหาของฉัน: https://github.com/sasha-ch/Aura.Sql อิงตามไลบรารี auraphp / Aura.Sql

ตัวอย่างการใช้งาน:

$q = "insert into t2(id,name) values (?,?), ... on duplicate key update name=name"; 
$bind_values = [ [[1,'str1'],[2,'str2']] ];
$pdo->perform($q, $bind_values);

Bugreports ยินดีต้อนรับ


จาก 2.4 คุณสามารถสร้างการแทรกได้หลายแบบด้วยgithub.com/auraphp/Aura.SqlQuery/tree/…และใช้ประโยชน์จากExtendedPdoเพื่อดำเนินการ :)
Hari KT

0

ตัวอย่างโลกแห่งความจริงของฉันเพื่อแทรกรหัสไปรษณีย์เยอรมันทั้งหมดลงในตารางว่าง (เพื่อเพิ่มชื่อเมืองในภายหลัง):

// obtain column template
$stmt = $db->prepare('SHOW COLUMNS FROM towns');
$stmt->execute();
$columns = array_fill_keys(array_values($stmt->fetchAll(PDO::FETCH_COLUMN)), null);
// multiple INSERT
$postcode = '01000';// smallest german postcode
while ($postcode <= 99999) {// highest german postcode
    $values = array();
    while ($postcode <= 99999) {
        // reset row
        $row = $columns;
        // now fill our row with data
        $row['postcode'] = sprintf('%05d', $postcode);
        // build INSERT array
        foreach ($row as $value) {
            $values[] = $value;
        }
        $postcode++;
        // avoid memory kill
        if (!($postcode % 10000)) {
            break;
        }
    }
    // build query
    $count_columns = count($columns);
    $placeholder = ',(' . substr(str_repeat(',?', $count_columns), 1) . ')';//,(?,?,?)
    $placeholder_group = substr(str_repeat($placeholder, count($values) / $count_columns), 1);//(?,?,?),(?,?,?)...
    $into_columns = implode(',', array_keys($columns));//col1,col2,col3
    // this part is optional:
    $on_duplicate = array();
    foreach ($columns as $column => $row) {
        $on_duplicate[] = $column;
        $on_duplicate[] = $column;
    }
    $on_duplicate = ' ON DUPLICATE KEY UPDATE' . vsprintf(substr(str_repeat(', %s = VALUES(%s)', $count_columns), 1), $on_duplicate);
    // execute query
    $stmt = $db->prepare('INSERT INTO towns (' . $into_columns . ') VALUES' . $placeholder_group . $on_duplicate);//INSERT INTO towns (col1,col2,col3) VALUES(?,?,?),(?,?,?)... {ON DUPLICATE...}
    $stmt->execute($values);
}

ในขณะที่คุณสามารถดูความยืดหยุ่นได้อย่างเต็มที่ คุณไม่จำเป็นต้องตรวจสอบจำนวนคอลัมน์หรือตรวจสอบว่าคอลัมน์ของคุณอยู่ในตำแหน่งใด คุณจะต้องตั้งค่าข้อมูลแทรก:

    $row['postcode'] = sprintf('%05d', $postcode);

ฉันภูมิใจในตัวสร้างสตริงข้อความค้นหาบางตัวเนื่องจากพวกเขาทำงานโดยไม่ต้องมีฟังก์ชั่นอาร์เรย์จำนวนมากเช่น array_merge โดยเฉพาะอย่างยิ่ง vsprintf () เป็นสิ่งที่ดี

ในที่สุดฉันก็ต้องเพิ่ม 2x ในขณะที่ () เพื่อหลีกเลี่ยงการเกินขีด จำกัด หน่วยความจำ ขึ้นอยู่กับขีด จำกัด หน่วยความจำของคุณ แต่วิธีแก้ปัญหาทั่วไปที่ดีเพื่อหลีกเลี่ยงปัญหา (และการมี 10 ข้อความค้นหาก็ยังดีกว่า 10.000)


0

test.php

<?php
require_once('Database.php');

$obj = new Database();
$table = "test";

$rows = array(
    array(
    'name' => 'balasubramani',
    'status' => 1
    ),
    array(
    'name' => 'balakumar',
    'status' => 1
    ),
    array(
    'name' => 'mani',
    'status' => 1
    )
);

var_dump($obj->insertMultiple($table,$rows));
?>

Database.php

<?php
class Database 
{

    /* Initializing Database Information */

    var $host = 'localhost';
    var $user = 'root';
    var $pass = '';
    var $database = "database";
    var $dbh;

    /* Connecting Datbase */

    public function __construct(){
        try {
            $this->dbh = new PDO('mysql:host='.$this->host.';dbname='.$this->database.'', $this->user, $this->pass);
            //print "Connected Successfully";
        } 
        catch (PDOException $e) {
            print "Error!: " . $e->getMessage() . "<br/>";
            die();
        }
    }
/* Insert Multiple Rows in a table */

    public function insertMultiple($table,$rows){

        $this->dbh->beginTransaction(); // also helps speed up your inserts.
        $insert_values = array();
        foreach($rows as $d){
            $question_marks[] = '('  . $this->placeholders('?', sizeof($d)) . ')';
            $insert_values = array_merge($insert_values, array_values($d));
            $datafields = array_keys($d);
        }

        $sql = "INSERT INTO $table (" . implode(",", $datafields ) . ") VALUES " . implode(',', $question_marks);

        $stmt = $this->dbh->prepare ($sql);
        try {
            $stmt->execute($insert_values);
        } catch (PDOException $e){
            echo $e->getMessage();
        }
        return $this->dbh->commit();
    }

    /*  placeholders for prepared statements like (?,?,?)  */

    function placeholders($text, $count=0, $separator=","){
        $result = array();
        if($count > 0){
            for($x=0; $x<$count; $x++){
                $result[] = $text;
            }
        }

        return implode($separator, $result);
    }

}
?>

ยินดีต้อนรับสู่ stackoverflow ไม่ใช่แค่รหัสโปรดโพสต์สิ่งที่เป็นปัญหาของคุณและอธิบาย
Prakash Palnati

เป็นพื้น มันเป็นเพียงการติดตั้งโค้ดที่ให้ไว้ในคำตอบที่ได้รับการยอมรับ
Your Common Sense

0

ฉันมีปัญหาเดียวกันและนี่คือวิธีที่ฉันทำเพื่อตัวเองและฉันก็ทำฟังก์ชั่นสำหรับตัวเอง (และคุณสามารถใช้มันได้หากมันช่วยคุณได้)

ตัวอย่าง:

INSERT INTO countries (ประเทศ, เมือง) ค่า (เยอรมนี, เบอร์ลิน), (ฝรั่งเศส, ปารีส);

$arr1 = Array("Germany", "Berlin");
$arr2 = Array("France", "France");

insertMultipleData("countries", Array($arr1, $arr2));


// Inserting multiple data to the Database.
public function insertMultipleData($table, $multi_params){
    try{
        $db = $this->connect();

        $beforeParams = "";
        $paramsStr = "";
        $valuesStr = "";

        for ($i=0; $i < count($multi_params); $i++) { 

            foreach ($multi_params[$i] as $j => $value) {                   

                if ($i == 0) {
                    $beforeParams .=  " " . $j . ",";
                }

                $paramsStr .= " :"  . $j . "_" . $i .",";                                       
            }

            $paramsStr = substr_replace($paramsStr, "", -1);
            $valuesStr .=  "(" . $paramsStr . "),"; 
            $paramsStr = "";
        }


        $beforeParams = substr_replace($beforeParams, "", -1);
        $valuesStr = substr_replace($valuesStr, "", -1);


        $sql = "INSERT INTO " . $table . " (" . $beforeParams . ") VALUES " . $valuesStr . ";";

        $stmt = $db->prepare($sql);


        for ($i=0; $i < count($multi_params); $i++) { 
            foreach ($multi_params[$i] as $j => &$value) {
                $stmt->bindParam(":" . $j . "_" . $i, $value);                                      
            }
        }

        $this->close($db);
        $stmt->execute();                       

        return true;

    }catch(PDOException $e){            
        return false;
    }

    return false;
}

// Making connection to the Database 
    public function connect(){
        $host = Constants::DB_HOST;
        $dbname = Constants::DB_NAME;
        $user = Constants::DB_USER;
        $pass = Constants::DB_PASS;

        $mysql_connect_str = 'mysql:host='. $host . ';dbname=' .$dbname;

        $dbConnection = new PDO($mysql_connect_str, $user, $pass);
        $dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

        return $dbConnection;
    }

    // Closing the connection
    public function close($db){
        $db = null;
    }

ถ้าinsertMultipleData ($ table, $ multi_params) จะส่งกลับค่า TRUEข้อมูลของคุณจะถูกแทรกลงในฐานข้อมูลของคุณ


0

จากการทดลองของฉันฉันพบว่า mysql แทรกคำสั่งที่มีหลายแถวของค่าในการทำรายการเดียวเป็นวิธีที่เร็วที่สุด

อย่างไรก็ตามหากข้อมูลมีค่ามากเกินไปmax_allowed_packetการตั้งค่าของ mysql อาจ จำกัด การแทรกธุรกรรมเดี่ยวด้วยแถวค่าหลายค่า ดังนั้นฟังก์ชั่นต่อไปนี้จะล้มเหลวเมื่อมีข้อมูลมากกว่าmax_allowed_packetขนาดของ mysql :

  1. singleTransactionInsertWithRollback
  2. singleTransactionInsertWithPlaceholders
  3. singleTransactionInsert

ที่ประสบความสำเร็จมากที่สุดในการแทรกสถานการณ์ข้อมูลขนาดใหญ่เป็นtransactionSpeedวิธีการ แต่มันใช้เวลามากขึ้นวิธีการดังกล่าวข้างต้น ดังนั้นเพื่อจัดการกับปัญหานี้คุณสามารถแยกข้อมูลของคุณเป็นชิ้นเล็ก ๆ และเรียกการทำธุรกรรมเดี่ยวแทรกหลายครั้งหรือเลิกใช้ความเร็วในการดำเนินการโดยใช้transactionSpeedวิธีการ

นี่คืองานวิจัยของฉัน

<?php

class SpeedTestClass
{
    private $data;

    private $pdo;

    public function __construct()
    {
        $this->data = [];
        $this->pdo = new \PDO('mysql:dbname=test_data', 'admin', 'admin');
        if (!$this->pdo) {
            die('Failed to connect to database');
        }
    }

    public function createData()
    {
        $prefix = 'test';
        $postfix = 'unicourt.com';
        $salutations = ['Mr.', 'Ms.', 'Dr.', 'Mrs.'];

        $csv[] = ['Salutation', 'First Name', 'Last Name', 'Email Address'];
        for ($i = 0; $i < 100000; ++$i) {
            $csv[] = [
                $salutations[$i % \count($salutations)],
                $prefix.$i,
                $prefix.$i,
                $prefix.$i.'@'.$postfix,
            ];
        }

        $this->data = $csv;
    }

    public function truncateTable()
    {
        $this->pdo->query('TRUNCATE TABLE `name`');
    }

    public function transactionSpeed()
    {
        $timer1 = microtime(true);
        $this->pdo->beginTransaction();
        $sql = 'INSERT INTO `name` (`first_name`, `last_name`) VALUES (:first_name, :last_name)';
        $sth = $this->pdo->prepare($sql);

        foreach (\array_slice($this->data, 1) as $values) {
            $sth->execute([
                ':first_name' => $values[1],
                ':last_name' => $values[2],
            ]);
        }

        // $timer2 = microtime(true);
        // echo 'Prepare Time: '.($timer2 - $timer1).PHP_EOL;
        // $timer3 = microtime(true);

        if (!$this->pdo->commit()) {
            echo "Commit failed\n";
        }
        $timer4 = microtime(true);
        // echo 'Commit Time: '.($timer4 - $timer3).PHP_EOL;

        return $timer4 - $timer1;
    }

    public function autoCommitSpeed()
    {
        $timer1 = microtime(true);
        $sql = 'INSERT INTO `name` (`first_name`, `last_name`) VALUES (:first_name, :last_name)';
        $sth = $this->pdo->prepare($sql);
        foreach (\array_slice($this->data, 1) as $values) {
            $sth->execute([
                ':first_name' => $values[1],
                ':last_name' => $values[2],
            ]);
        }
        $timer2 = microtime(true);

        return $timer2 - $timer1;
    }

    public function noBindAutoCommitSpeed()
    {
        $timer1 = microtime(true);

        foreach (\array_slice($this->data, 1) as $values) {
            $sth = $this->pdo->prepare("INSERT INTO `name` (`first_name`, `last_name`) VALUES ('{$values[1]}', '{$values[2]}')");
            $sth->execute();
        }
        $timer2 = microtime(true);

        return $timer2 - $timer1;
    }

    public function singleTransactionInsert()
    {
        $timer1 = microtime(true);
        foreach (\array_slice($this->data, 1) as $values) {
            $arr[] = "('{$values[1]}', '{$values[2]}')";
        }
        $sth = $this->pdo->prepare('INSERT INTO `name` (`first_name`, `last_name`) VALUES '.implode(', ', $arr));
        $sth->execute();
        $timer2 = microtime(true);

        return $timer2 - $timer1;
    }

    public function singleTransactionInsertWithPlaceholders()
    {
        $placeholders = [];
        $timer1 = microtime(true);
        $sql = 'INSERT INTO `name` (`first_name`, `last_name`) VALUES ';
        foreach (\array_slice($this->data, 1) as $values) {
            $placeholders[] = '(?, ?)';
            $arr[] = $values[1];
            $arr[] = $values[2];
        }
        $sql .= implode(', ', $placeholders);
        $sth = $this->pdo->prepare($sql);
        $sth->execute($arr);
        $timer2 = microtime(true);

        return $timer2 - $timer1;
    }

    public function singleTransactionInsertWithRollback()
    {
        $placeholders = [];
        $timer1 = microtime(true);
        $sql = 'INSERT INTO `name` (`first_name`, `last_name`) VALUES ';
        foreach (\array_slice($this->data, 1) as $values) {
            $placeholders[] = '(?, ?)';
            $arr[] = $values[1];
            $arr[] = $values[2];
        }
        $sql .= implode(', ', $placeholders);
        $this->pdo->beginTransaction();
        $sth = $this->pdo->prepare($sql);
        $sth->execute($arr);
        $this->pdo->commit();
        $timer2 = microtime(true);

        return $timer2 - $timer1;
    }
}

$s = new SpeedTestClass();
$s->createData();
$s->truncateTable();
echo "Time Spent for singleTransactionInsertWithRollback: {$s->singleTransactionInsertWithRollback()}".PHP_EOL;
$s->truncateTable();
echo "Time Spent for single Transaction Insert: {$s->singleTransactionInsert()}".PHP_EOL;
$s->truncateTable();
echo "Time Spent for single Transaction Insert With Placeholders: {$s->singleTransactionInsertWithPlaceholders()}".PHP_EOL;
$s->truncateTable();
echo "Time Spent for transaction: {$s->transactionSpeed()}".PHP_EOL;
$s->truncateTable();
echo "Time Spent for AutoCommit: {$s->noBindAutoCommitSpeed()}".PHP_EOL;
$s->truncateTable();
echo "Time Spent for autocommit with bind: {$s->autoCommitSpeed()}".PHP_EOL;
$s->truncateTable();

ผลลัพธ์สำหรับ 100,000 รายการสำหรับตารางที่มีเพียงสองคอลัมน์มีดังนี้

$ php data.php
Time Spent for singleTransactionInsertWithRollback: 0.75147604942322
Time Spent for single Transaction Insert: 0.67445182800293
Time Spent for single Transaction Insert With Placeholders: 0.71131205558777
Time Spent for transaction: 8.0056409835815
Time Spent for AutoCommit: 35.4979159832
Time Spent for autocommit with bind: 33.303519010544

0

สิ่งนี้ใช้ได้สำหรับฉัน

$sql = 'INSERT INTO table(pk_pk1,pk_pk2,date,pk_3) VALUES '; 
$qPart = array_fill(0, count($array), "(?, ?,UTC_TIMESTAMP(),?)");
$sql .= implode(",", $qPart);
$stmt =    DB::prepare('base', $sql);
$i = 1;
foreach ($array as $value) { 
  $stmt->bindValue($i++, $value);
  $stmt->bindValue($i++, $pk_pk1);
  $stmt->bindValue($i++, $pk_pk2); 
  $stmt->bindValue($i++, $pk_pk3); 
} 
$stmt->execute();

0

สิ่งที่เกี่ยวกับสิ่งนี้:

        if(count($types_of_values)>0){
         $uid = 1;
         $x = 0;
         $sql = "";
         $values = array();
          foreach($types_of_values as $k=>$v){
            $sql .= "(:id_$k,:kind_of_val_$k), ";
            $values[":id_$k"] = $uid;
            $values[":kind_of_val_$k"] = $v;
          }
         $sql = substr($sql,0,-2);
         $query = "INSERT INTO table (id,value_type) VALUES $sql";
         $res = $this->db->prepare($query);
         $res->execute($values);            
        }

แนวคิดเบื้องหลังนี้คือการวนรอบค่าอาร์เรย์ของคุณเพิ่ม "หมายเลข id" ลงในแต่ละลูปสำหรับตัวยึดคำสั่งที่เตรียมไว้ของคุณในขณะเดียวกันคุณเพิ่มค่าลงในอาร์เรย์ของคุณสำหรับพารามิเตอร์การรวม หากคุณไม่ต้องการใช้ดัชนี "คีย์" จากอาร์เรย์คุณสามารถเพิ่ม $ i = 0 และ $ i ++ ภายในลูป อาจใช้งานได้ในตัวอย่างนี้แม้ว่าคุณจะมีอาร์เรย์ที่เชื่อมโยงกับคีย์ที่ตั้งชื่อไว้ แต่ก็ยังคงใช้งานได้หากให้คีย์นั้นไม่ซ้ำกัน ด้วยงานเล็ก ๆ น้อย ๆ มันจะดีสำหรับอาร์เรย์ที่ซ้อนกันเช่นกัน

** โปรดทราบว่าสตริงย่อยจะตัดตัวแปร $ sql ออกจากช่องว่างและเครื่องหมายจุลภาคหากคุณไม่มีช่องว่างคุณต้องเปลี่ยนเป็น -1 แทนที่จะเป็น -2


-1

โซลูชันส่วนใหญ่ที่ให้ไว้ที่นี่เพื่อสร้างคิวรีที่เตรียมไว้นั้นซับซ้อนกว่าที่พวกเขาต้องการ การใช้ PHP ที่มีอยู่ในฟังก์ชั่นทำให้คุณสามารถสร้างคำสั่ง SQL ได้อย่างง่ายดายโดยไม่ต้องมีค่าใช้จ่ายมากนัก

กำหนด$recordsอาร์เรย์ของเรกคอร์ดที่แต่ละเรกคอร์ดเป็นตัวจัดทำดัชนี (ในรูปแบบของfield => value), ฟังก์ชันต่อไปนี้จะแทรกระเบียนลงในตารางที่กำหนด$tableบนการเชื่อมต่อ PDO $connectionโดยใช้คำสั่งที่เตรียมไว้เพียงครั้งเดียว โปรดทราบว่านี่เป็นวิธีการแก้ปัญหา PHP 5.6+ เนื่องจากมีการใช้อาร์กิวเมนต์ที่เปิดออกในการโทรไปที่array_push:

private function import(PDO $connection, $table, array $records)
{
    $fields = array_keys($records[0]);
    $placeHolders = substr(str_repeat(',?', count($fields)), 1);
    $values = [];
    foreach ($records as $record) {
        array_push($values, ...array_values($record));
    }

    $query = 'INSERT INTO ' . $table . ' (';
    $query .= implode(',', $fields);
    $query .= ') VALUES (';
    $query .= implode('),(', array_fill(0, count($records), $placeHolders));
    $query .= ')';

    $statement = $connection->prepare($query);
    $statement->execute($values);
}

1
ไม่ควรใช้รหัสนี้เนื่องจากมีความเสี่ยงต่อการฉีด SQL
สามัญสำนึกของคุณ
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.