返回

PDO批量插入多行数据:正确姿势与性能优化

mysql

一次性插入多行数据:PDO 批量插入的正确姿势

遇到需要一次性向数据库插入多行记录的情况,结果发现代码跑不通?别急,问题出在你构建和执行 SQL 语句的方式上。咱先直接看原始代码的问题。

原始代码的问题

原代码的意图很清楚,想要把表单提交过来的多个姓名、数字和性别数据插入到 memo 表里。 看起来挺合理,但犯了几个错误:

  1. SQL 注入漏洞 :直接把经过 input_checker_ 函数处理后的变量拼接到 SQL 语句里,太危险了! 哪怕做了转义,也还是有被注入的风险。
  2. 循环内重复执行 INSERTforeach 循环里面,每次都执行一个单独的 INSERT 语句。如果有 100 条数据,就要执行 100 次数据库操作,效率低到爆!
  3. 变量取值错误 : 原代码中 $info 未定义,而且表单数据的键值索引也没有正确处理,$name$number$gender的值始终都只处理一个数据.

怎么解决?——正确使用 PDO 批量插入

要高效且安全地插入多行数据,得靠 PDO 的预处理语句(Prepared Statements)和事务处理。咱分几步走:

1. 准备阶段:构建 SQL 模板

咱们先把 SQL 语句写成一个“模板”,用占位符(? 或者 :name 这样的命名占位符)来代替实际的值。像这样:

$sql = "INSERT INTO memo (name, number, gender) VALUES (?, ?, ?)";

这里有三个字段 (name, number, gender), 所以每个 VALUES 有三个 ?.

2. 绑定参数,循环执行

预处理的精髓,就是先“准备”好 SQL 语句(prepare() 方法),然后反复“执行”它(execute() 方法),每次执行时传入不同的参数。

基础版:

<?php

$servername = "";
$username = "";
$password = "";
$dbname = "";

try {
    $conn = new PDO("mysql:host=$servername;dbname=$dbname", $username, $password);
    $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    if ($_SERVER["REQUEST_METHOD"] == "POST") {

        $stmt = $conn->prepare("INSERT INTO memo (name, number, gender) VALUES (?, ?, ?)");

        // 获取提交的数据数量. 假设三个数组长度一致
        $count = count($_POST["name"]);

        for ($i = 0; $i < $count; $i++) {
              $name = input_checker($_POST["name"][$i]);
              $number = input_checker($_POST["number"][$i]);
              $gender = input_checker($_POST["gender"][$i]);
              $stmt->execute([$name, $number, $gender]);
        }
        echo "多行数据插入成功!";
    }
} catch(PDOException $e) {
    echo "出错了:" . $e->getMessage();
}

$conn = null;

function input_checker($data) {
    $data = trim($data);
    $data = stripslashes($data);
    $data = htmlspecialchars($data);
    return $data;
}
?>
<form method="post" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]);?>">
Name 1: <input type="text" name="name[]"><br>
Name 2: <input type="text" name="name[]"><br>
Name 3: <input type="text" name="name[]"><br>
Number 1: <input type="text" name="number[]"><br>
Number 2: <input type="text" name="number[]"><br>
Number 3: <input type="text" name="number[]"><br>
Gender 1: <input type="text" name="gender[]"><br>
Gender 2: <input type="text" name="gender[]"><br>
Gender 3: <input type="text" name="gender[]"><br>

<input type="submit" name="submit" value="Submit">
</form>

原理:

  • $conn->prepare(...): 准备好 SQL 语句,并返回一个 PDOStatement 对象(这里是 $stmt)。
  • $stmt->execute([$name, $number, $gender]): 给占位符绑定实际的值,然后执行 SQL 语句。 这里用的 ? 占位符,所以传一个索引数组进去。

通过使用for循环迭代提交数据,循环调用execute方法实现多行数据处理.

3. 更进一步:单次 SQL 执行与事务处理

其实还可以优化!如果数据库支持,可以把多行数据合并成一个 SQL 语句,一次性执行。

进阶版:

<?php

$servername = "";
$username = "";
$password = "";
$dbname = "";

try {
    $conn = new PDO("mysql:host=$servername;dbname=$dbname", $username, $password);
    $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    if ($_SERVER["REQUEST_METHOD"] == "POST") {

        $count = count($_POST["name"]);
        if ($count > 0) {

            //构建多个值的SQL语句
            $values = [];
            $params = [];
            for ($i = 0; $i < $count; $i++) {
                $values[] = "(?, ?, ?)";
                $params[] = input_checker($_POST["name"][$i]);
                $params[] = input_checker($_POST["number"][$i]);
                $params[] = input_checker($_POST["gender"][$i]);
            }

            $sql = "INSERT INTO memo (name, number, gender) VALUES " . implode(",", $values);
            $stmt = $conn->prepare($sql);
            $stmt->execute($params);

             echo "多行数据插入成功!";
        }
    }
} catch(PDOException $e) {

     echo "出错了: " . $e->getMessage();

}

$conn = null;

function input_checker($data) {
    $data = trim($data);
    $data = stripslashes($data);
    $data = htmlspecialchars($data);
    return $data;
}
?>

<form method="post" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]);?>">
Name 1: <input type="text" name="name[]"><br>
Name 2: <input type="text" name="name[]"><br>
Name 3: <input type="text" name="name[]"><br>
Number 1: <input type="text" name="number[]"><br>
Number 2: <input type="text" name="number[]"><br>
Number 3: <input type="text" name="number[]"><br>
Gender 1: <input type="text" name="gender[]"><br>
Gender 2: <input type="text" name="gender[]"><br>
Gender 3: <input type="text" name="gender[]"><br>

<input type="submit" name="submit" value="Submit">
</form>

改进点解释:

  • 循环构建 values 数组: 构建 (?, ?, ?) 多个占位符组成的SQL语句.
  • implode(",", $values): 把多个 (?, ?, ?) 用逗号连接起来, 生成最终 VALUES 部分。
  • 一次 execute: 将参数数组传入执行即可.

这种方式比之前的多次 execute 效率更高, 因为只需要跟数据库交互一次。

更安全的操作--事务 :

<?php

$servername = "";
$username = "";
$password = "";
$dbname = "";

try {
    $conn = new PDO("mysql:host=$servername;dbname=$dbname", $username, $password);
    $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    if ($_SERVER["REQUEST_METHOD"] == "POST") {

        $count = count($_POST["name"]);
        if($count > 0) {

             // 开始事务
            $conn->beginTransaction();

            $values = [];
            $params = [];

            for($i=0; $i<$count; $i++) {
                $values[] = "(?, ?, ?)";
                $params[] = input_checker($_POST["name"][$i]);
                $params[] = input_checker($_POST["number"][$i]);
                $params[] = input_checker($_POST["gender"][$i]);
            }

             $sql = "INSERT INTO memo (name, number, gender) VALUES " . implode(",", $values);
             $stmt = $conn->prepare($sql);
             $stmt->execute($params);
              // 提交事务
             $conn->commit();
             echo "多行数据插入成功!";
         }

    }

} catch(PDOException $e) {
     // 出错时回滚
     if ($conn) {
          $conn->rollBack();
     }
     echo "出错了: " . $e->getMessage();
}
$conn = null;

function input_checker($data) {
    $data = trim($data);
    $data = stripslashes($data);
    $data = htmlspecialchars($data);
    return $data;
}
?>
<form method="post" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]);?>">
Name 1: <input type="text" name="name[]"><br>
Name 2: <input type="text" name="name[]"><br>
Name 3: <input type="text" name="name[]"><br>
Number 1: <input type="text" name="number[]"><br>
Number 2: <input type="text" name="number[]"><br>
Number 3: <input type="text" name="number[]"><br>
Gender 1: <input type="text" name="gender[]"><br>
Gender 2: <input type="text" name="gender[]"><br>
Gender 3: <input type="text" name="gender[]"><br>
<input type="submit" name="submit" value="Submit">
</form>

加入事务:

  • $conn->beginTransaction();: 开启一个事务。
  • $conn->commit();: 所有操作都成功,就提交事务,数据才会真正写入数据库。
  • $conn->rollBack();: 如果中间有任何一步出错,就回滚事务,撤销所有操作,保证数据一致性。

用事务的好处很明显:保证一批操作要么全部成功,要么全部失败,不会出现一部分数据插进去了、一部分没插进去的情况.

其他补充说明

  • input_checker 函数 : 只是做了最基本的转义, 如果想对用户的输入数据格式有校验需求 (比如必须是数字等), 在此函数增加即可.
  • HTML 部分 : name 属性用数组形式 (name[], number[], gender[]), 这样 PHP 才能接收到多个值. 注意三个[]的输入数量应该相等.
  • 命名占位符 除了 ?, 也可以使用 :name 这类. 绑定时候对应调整:
  $stmt->execute([':name' => $name, ':number' => $number, ':gender' => $gender]);

总之记住两点:用预处理语句尽量合并 SQL 执行 。这样,你就能轻松搞定 PDO 批量插入多行数据!