PHP PDO相关

PDO 中对于错误的处理方式

  • PDO::ERRMODE_SILENT:默认方式,可通过检测函数返回值判断执行结果,当返回 false 时可通过 getCode、errorCode、errorInfo等方法获取具体错误信息
  • PDO::ERRMODE_WARNING:使用这个模式时,PDO将会发出一个传统的E_WARNING信息
  • PDO::ERRMODE_EXCEPTION:发生错误时 PDO 会抛出 PDOException,捕捉它并在 catch 块中进行 rollback 或其它业务的处理

commit 的成功不等于业务的成功

场景会员注册,需要插入两个表 tm、ta 的数据成功才成功。在 PDO 错误的操作中以为 commit 成功就是 commit 之前的数据操作都成功,或者说以为 commit 之前出现不成功的数据操作,commit 就会不成功,实际上并没有直接关系,因为 commit 结果仅对其自身负责,并不表示完整业务的结果。

// 错误的示范
// 假设 tm 表的 id 字段不允许重复且已经存在 id=0005 的记录,因此 $sql0 执行会出错,ta 表不存在重复索引
$sql0 = 'insert into tm(`id`) values (:id);';
$sql1 = 'insert into ta(`id`) values (:id);';

$pdo = new PDO(dsn, user, pwd);
$pdo->beginTransaction();
// prepare 不会出错,因为sql语法正确
$stmt = $pdo->prepare($sql0);
// execute 会出错, execute 返回 false
$stmt->execute(array(':id'=>'0005'));

$stmt=$pdo->prepare($sql1);
// ta 表执行成功,返回 true
$stmt->execute(array(':id'=>'0005'));

// 这里 commit 返回 true
$ok = $pdo->commit();
if($ok){
// 根据 $ok 判断,导致了误以为整个数据操作正确,实际 $sql0 业务执行错误,仅 $sql1 执行成功
}

解决方案一:显式设置 PDO 错误处理模式,使其暴露 PDOException,在业务中捕捉并处理,达到要么成功要么回滚的要求。

// 解决方案一的示范
// 假设 tm 表的 id 字段不允许重复且已经存在 id=0005 的记录,因此 $sql0 执行会出错,ta 表不存在重复索引
$sql0 = 'insert into tm(`id`) values (:id);';
$sql1 = 'insert into ta(`id`) values (:id);';

// 在构造实例时设置 PDO 发生错误的处理方式是 PDOException
// 也可以在构造之后,使用 $pdo->setAttribute 方法设置
$pdo = new PDO(dsn, user, pwd, [PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION]);
try {
    $pdo->beginTransaction();
    // prepare 不会出错,因为sql语法正确
    $stmt = $pdo->prepare($sql0);
    // execute 会出错, execute 返回 false
    // 因此到这里就会抛出 PDOException 被 catch
    // 不会往下执行
    $stmt->execute(array(':id' => '0005'));

    $stmt = $pdo->prepare($sql1);
    // ta 表执行成功,返回 true
    $stmt->execute(array(':id' => '0005'));

    // 这里 commit 返回 true
    $ok = $pdo->commit();
} catch (PDOException $e) {
    $pdo->rollBack();
}

解决方案二:不改变默认模式,手动判断各个方法的返回值,当执行不成功也就是 false 时进行回滚处理,控制完整的业务流程。

// 解决方案二的示范
// 假设 tm 表的 id 字段不允许重复且已经存在 id=0005 的记录,因此 $sql0 执行会出错,ta 表不存在重复索引
$sql0 = 'insert into tm(`id`) values (:id);';
$sql1 = 'insert into ta(`id`) values (:id);';

$pdo = new PDO(dsn, user, pwd);

$pdo->beginTransaction();
// prepare 不会出错,因为sql语法正确
$stmt = $pdo->prepare($sql0);
// execute 会出错, execute 返回 false
// 立即处理或标记返回值之后处理
$ok0 = $stmt->execute(array(':id' => '0005'));

$stmt = $pdo->prepare($sql1);
// ta 表执行成功,返回 true
$ok1 = $stmt->execute(array(':id' => '0005'));

if ($ok0 && $ok1) {
    $ok = $pdo->commit();
} else {
    $pdo->rollBack();
}
posted @ 2021-01-29 13:59  试试手气  阅读(52)  评论(0编辑  收藏  举报