PDO 오류 처리 모드의 종류와 특성
PHP의 PDO는 데이터베이스 작업 중 발생하는 오류를 다양한 방식으로 처리할 수 있도록 세 가지 오류 모드를 제공합니다. 각각의 모드는 애플리케이션의 안정성과 디버깅 효율에 직접적인 영향을 미칩니다.
- PDO::ERRMODE_SILENT: 기본 동작 모드로, 오류가 발생해도 별도의 경고나 예외를 발생시키지 않습니다. 개발자는 각 메서드의 반환 값을 수동으로 확인하여 성공 여부를 판단해야 하며, 필요 시
errorCode()또는errorInfo()메서드를 통해 구체적인 오류 정보를 조회할 수 있습니다. - PDO::ERRMODE_WARNING: SQL 오류 발생 시 PHP의 일반적인
E_WARNING수준의 경고를 출력합니다. 로그 기록에는 유용하지만, 프로그램 흐름을 자동으로 중단시키지는 않으므로 주의가 필요합니다. - PDO::ERRMODE_EXCEPTION: 모든 오류 상황에서
PDOException을 던지며, 이로 인해 코드 실행 흐름은 가장 가까운try-catch블록으로 제어됩니다. 트랜잭션 처리 시 권장되는 방식입니다.
commit()의 의미에 대한 오해와 현실
많은 개발자들이 commit() 메서드가 true를 반환하면 그 이전의 모든 쿼리가 성공적으로 완료된 것으로 간주합니다. 그러나 이는 잘못된 이해입니다. commit()은 트랜잭션 자체의 커밋 가능 여부만을 판단하며, 이전 단계의 개별 쿼리 성공 여부를 보증하지 않습니다.
예를 들어, 회원가입 과정에서 두 개의 테이블(members, accounts)에 동시에 데이터 삽입이 성공해야 하는 경우, 첫 번째 삽입이 실패하더라도 두 번째가 성공하고 마지막에 commit()이 호출되면, 실제론 불완전한 상태로 커밋될 위험이 있습니다.
// 잘못된 예시: commit() 결과만으로 전체 성공을 판단
$memberSql = "INSERT INTO members (uid) VALUES (?)";
$accountSql = "INSERT INTO accounts (uid) VALUES (?)";
$pdo = new PDO($dsn, $user, $password);
$pdo->beginTransaction();
$stmt1 = $pdo->prepare($memberSql);
$result1 = $stmt1->execute(['U0001']); // 실패 가능 (중복 키 등)
$stmt2 = $pdo->prepare($accountSql);
$result2 = $stmt2->execute(['U0001']); // 성공
// 여기서 commit()은 트랜잭션 자체가 유효하므로 true를 반환할 수 있음
if ($pdo->commit()) {
echo "트랜잭션이 커밋되었지만, 일부 작업은 실패했을 수 있음.";
}
해결책 1: 예외 기반 처리 (권장)
가장 견고한 방법은 PDO::ERRMODE_EXCEPTION을 설정하여 오류 발생 시 즉시 예외를 발생시키도록 하는 것입니다. 이를 통해 트랜잭션 내 어떤 단계에서든 실패가 발생하면 자동으로 롤백 처리가 가능합니다.
// 권장되는 예외 기반 패턴
$pdo = new PDO($dsn, $user, $password, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
]);
try {
$pdo->beginTransaction();
$stmt1 = $pdo->prepare("INSERT INTO members (uid) VALUES (?)");
$stmt1->execute(['U0001']); // 실패 시 즉시 예외 발생
$stmt2 = $pdo->prepare("INSERT INTO accounts (uid) VALUES (?)");
$stmt2->execute(['U0001']);
$pdo->commit();
} catch (PDOException $e) {
$pdo->rollback();
error_log("회원가입 실패: " . $e->getMessage());
}
해결책 2: 명시적 반환 값 검사
기본 모드를 유지하면서도 안정성을 확보하려면, 모든 실행 단계의 반환 값을 직접 평가하고, 하나라도 실패하면 롤백하는 절차를 수동으로 구현해야 합니다.
// 반환 값 기반 수동 제어
$pdo = new PDO($dsn, $user, $password);
$pdo->beginTransaction();
$success = true;
$stmt1 = $pdo->prepare("INSERT INTO members (uid) VALUES (?)");
if (!$stmt1->execute(['U0001'])) {
$success = false;
}
$stmt2 = $pdo->prepare("INSERT INTO accounts (uid) VALUES (?)");
if (!$stmt2->execute(['U0001'])) {
$success = false;
}
if ($success) {
$pdo->commit();
} else {
$pdo->rollback();
}
이 방식은 더 많은 코드가 필요하지만, 예외를 사용하지 않는 환경이나 세밀한 오류 처리가 필요한 경우 유용합니다.