PHP MySQL 插入多条数据

Multi-row INSERT, transaction wrapping, and rollback on failure.

Beginner
Key PointsA bulk insert should be all-or-nothing — a transaction guarantees it.

Multi-row insert

PHP
<?php
$pdo = new PDO($dsn, $user, $pass, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);

$rows = [
    ["Amy",  "amy@example.com",  25],
    ["Bob",  "bob@example.com",  28],
    ["Cara", "cara@example.com", 31],
];

$placeholders = implode(",", array_fill(0, count($rows), "(?,?,?)"));
$stmt = $pdo->prepare("INSERT INTO users (name, email, age) VALUES $placeholders");
$stmt->execute(array_merge(...$rows));

echo "批量插入 " . $stmt->rowCount() . " 行\n";
echo "最后 ID: " . $pdo->lastInsertId() . "\n";
Output
批量插入 3 行
最后 ID: 3

One statement for three rows means one round trip and one parse — an order of magnitude faster than looping execute.

Wrapping in a transaction

PHP
<?php
$rows = [
    ["Amy",  "amy@example.com",  25],
    ["Bob",  "bob@example.com",  28],
    ["Cara", "cara@example.com", 31],
];

$pdo->beginTransaction();

try {
    $placeholders = implode(",", array_fill(0, count($rows), "(?,?,?)"));
    $stmt = $pdo->prepare("INSERT INTO users (name, email, age) VALUES $placeholders");
    $stmt->execute(array_merge(...$rows));

    $pdo->commit();
    echo "提交成功,插入 " . $stmt->rowCount() . " 行\n";
} catch (Throwable $e) {
    $pdo->rollBack();
    echo "已回滚: " . $e->getMessage() . "\n";
}
Output
提交成功,插入 3 行

A single INSERT is already atomic. Transactions matter for combinations — inserting an order while decrementing stock must succeed together.

Batching in a loop

PHP
<?php
$all = [];
for ($i = 1; $i <= 2500; $i++) {
    $all[] = ["user$i", "user$i@example.com", 20 + $i % 30];
}

$chunkSize = 1000;
$total = 0;

$pdo->beginTransaction();
try {
    foreach (array_chunk($all, $chunkSize) as $chunk) {
        $ph = implode(",", array_fill(0, count($chunk), "(?,?,?)"));
        $stmt = $pdo->prepare("INSERT INTO users (name, email, age) VALUES $ph");
        $stmt->execute(array_merge(...$chunk));
        $total += $stmt->rowCount();
    }
    $pdo->commit();
} catch (Throwable $e) {
    $pdo->rollBack();
    exit("失败: " . $e->getMessage());
}

echo "共插入 $total 行,每批 $chunkSize 行\n";
Output
共插入 2500 行,每批 1000 行
Huge transactions bloat the undo log and make rollback slow. Commit in chunks of a few thousand rows for large imports.

LAST_INSERT_ID with batches

After a multi-row insert, lastInsertId() returns the first generated ID; the rest are consecutive. Easy to misremember.

PHP
<?php
$rows = [["X1","x1@e.com",20],["X2","x2@e.com",21],["X3","x3@e.com",22]];
$ph = implode(",", array_fill(0, count($rows), "(?,?,?)"));
$stmt = $pdo->prepare("INSERT INTO users (name, email, age) VALUES $ph");
$stmt->execute(array_merge(...$rows));

$first = (int)$pdo->lastInsertId();
$count = $stmt->rowCount();

echo "首个 ID: $first\n";
for ($i = 0; $i < $count; $i++) {
    echo "  " . ($first + $i) . " => " . $rows[$i][0] . "\n";
}
Output
首个 ID: 4
  4 => X1
  5 => X2
  6 => X3

Contiguity assumes innodb_autoinc_lock_mode 0 or 1 and no concurrent inserts. Under load IDs may have gaps — never rely on this in business logic.

Importing CSV

PHP
<?php
$csv = "name,email,age\nAmy,amy@example.com,25\nBob,bob@example.com,28\n";
$tmp = __DIR__ . "/import.csv";
file_put_contents($tmp, $csv);

$fp = fopen($tmp, "r");
fgetcsv($fp);                 // 跳过表头

$rows = [];
while (($r = fgetcsv($fp)) !== false) {
    $rows[] = [$r[0], $r[1], (int)$r[2]];
}
fclose($fp);
unlink($tmp);

echo "读到 " . count($rows) . " 行\n";
echo $rows[0][0] . " " . $rows[1][2] . "\n";
Output
读到 2 行
Amy 28

fgetcsv handles quoted fields containing commas. Hand-rolled explode(",") breaks on "Smith, John".

Friends & Partners