登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  php教程

PHP PDO 预处理批量写入的事务策略

来源:17golang原创

时间:2026-10-02 17:53:33 143浏览 收藏

PHP PDO 批量写入最稳妥的组合是“预处理语句 + 明确事务边界 + 异常统一回滚”:SQL 模板只准备一次,每条数据通过 execute() 传入参数;一个业务批次成功后只调用一次 commit(),任何一行失败都进入 rollback()。这样可以避免半批数据落库,也能让重试边界清楚。

官方手册:https://www.php.net/manual/en/pdo.transactions.php

要点速览
  • 预处理解决参数传递,不能替代事务。
  • 批次内所有 execute 共用同一个事务,commit 只放在全部成功之后。
  • 只在确认事务仍处于活动状态时 rollback;提交成功后再写“已完成”标记。

先固定模板,再让每行数据只承担参数

图1:php-pdo-batch-boundary-retry
图1:php-pdo-batch-boundary-retry

批量导入通常有固定字段,例如订单号、状态和创建时间。不要把数组值拼接进 SQL;准备好的语句既能保持结构稳定,也方便把错误定位到具体输入。

$sql = 'INSERT INTO import_items (item_code, state, created_at)
        VALUES (:item_code, :state, :created_at)';
$statement = $pdo->prepare($sql);

// 每轮只绑定当前记录,SQL 结构不会随输入变化。
foreach ($items as $item) {
    $statement->execute([
        ':item_code' => $item['code'],
        ':state' => $item['state'],
        ':created_at' => $item['created_at'],
    ]);
}

如果使用命名参数,数组键和 SQL 占位符要保持一致;不要在循环内反复 prepare(),除非每一行确实需要不同的 SQL 结构。

PHP PDO 预处理语句把批量记录参数绑定到统一 INSERT 模板的静态关系说明图
图1:PDO 预处理语句与批量参数的关系说明图,不是数据库截图。

用一个事务包住可回滚的业务批次

图2:php-pdo-prepare-transaction-structure
图2:php-pdo-prepare-transaction-structure

事务边界应跟业务批次一致,而不是简单地“每条一提交”。每条提交会放大日志与网络开销,也无法保证整批一致;但一次事务包住数百万行又会拉长锁和日志占用,所以通常需要按数量或业务分片。

$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$statement = $pdo->prepare($sql);
$pdo->beginTransaction();

try {
    foreach ($items as $item) {
        // execute 失败直接抛异常,后续行不再继续写入。
        $statement->execute([
            ':item_code' => $item['code'],
            ':state' => $item['state'],
            ':created_at' => $item['created_at'],
        ]);
    }
    // 只有整批通过后才提交一次。
    $pdo->commit();
} catch (Throwable $error) {
    // 事务仍活动时才回滚,避免覆盖原始异常。
    if ($pdo->inTransaction()) {
        $pdo->rollBack();
    }
    throw $error;
}

这里的 inTransaction() 是保护动作,不是成功判断。真正的成功信号是 commit() 没有抛出异常;调用方应在它之后再更新导入批次状态、发送完成消息或写入审计记录。

PHP PDO 批量写入从 beginTransaction 到 commit 或 rollback 的生命周期结构说明图
图2:PDO 批量事务生命周期说明图,展示成功提交与异常回滚的边界。

批次大小、DDL 和重试要分开设计

数据量较大时可以把输入切成固定数量的批次,每一批独立开始和提交。批次大小不是越大越好,应结合行大小、锁等待、事务日志和失败重跑成本调整。DDL 也不要混在导入事务里:不同数据库引擎对 DDL 的隐式提交规则不同,迁移脚本应单独发布。

场景建议边界完成标记
普通批量导入按数量切片,一个切片一个事务commit 成功后记录批次号
单行不能失败整批回滚并保存失败输入修复后用幂等键重跑
大事务压力高降低批次大小,观察锁与日志每批独立可恢复

重试前要确认上一次提交结果。若请求在网络层超时,客户端未必知道服务端是否已经提交;这时应使用业务唯一键、导入批次号或幂等表判断,而不是直接盲目重放。

常见问题

预处理语句能保证批量写入原子性吗?

不能。它主要负责 SQL 模板和参数传递,原子性由事务边界决定。

catch 里一定要 rollback 吗?

只在连接仍处于活动事务时回滚,并保留原异常;否则可能产生新的回滚异常,反而遮住首个失败原因。

commit 成功后还需要重试吗?

正常不需要。只有调用方无法确认提交结果时,才应先按幂等键查询批次状态,再决定补偿动作。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>