본문 바로가기

비개발자의 개발 일지

현장 팀장에서 수출 ERP까지 — AI와 함께 시스템을 만든 기록

시리즈 보기
php

PHP PDO INSERT 트랜잭션 — beginTransaction · commit · rollBack 완전 정리

by 왕진 2026. 7. 8.
반응형

 

 

Web Development · PHP PDO

PHP PDO INSERT 트랜잭션 — beginTransaction · commit · rollBack 완전 정리

form.html로 받은 데이터를 PDO 트랜잭션으로 안전하게 INSERT — 왜 commit이 필요한가, rollBack은 언제 작동하는가
시작 오늘 분석할 코드

HTML 폼에서 성·이름·연령을 POST로 전송하고, PHP(view.php)에서 PDO를 이용해 MySQL에 INSERT하는 2파일 구조다. 핵심은 beginTransaction()execute()commit() / rollBack() 흐름이다.

form.html — 입력 폼
<FORM name="form1" method="post" action="view.php"> 성:<BR> <INPUT type="text" name="last_name"><BR> 이름:<BR> <INPUT type="text" name="first_name"><BR> 연령:<BR> <INPUT type="text" name="age"><BR> <INPUT type="submit" value="송신"> </FORM>
view.php — PDO INSERT + 트랜잭션
$dsn = "mysql:host=localhost;dbname=sampledb;charset=utf8"; try { $pdo = new PDO($dsn, "root", ""); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); } catch (PDOException $e) { die('오류: ' . $e->getMessage()); } try { $pdo->beginTransaction(); // 트랜잭션 시작 $sql = "INSERT INTO member (last_name, first_name, age) VALUES (:last_name, :first_name, :age)"; $stmh = $pdo->prepare($sql); $stmh->bindValue(':last_name', $_POST['last_name'], PDO::PARAM_STR); $stmh->bindValue(':first_name', $_POST['first_name'], PDO::PARAM_STR); $stmh->bindValue(':age', $_POST['age'], PDO::PARAM_INT); $stmh->execute(); // DB 처리 실행 $pdo->commit(); // 트랜잭션 확정 print "데이터를 " . $stmh->rowCount() . "건 입력하였습니다."; } catch (PDOException $e) { $pdo->rollBack(); // 에러 시 원상복구 print "오류: " . $e->getMessage(); }
실제 출력 미리보기
form.html 화면
성:
이름:
연령:
view.php 출력 (성공 시)
접속하였습니다.
데이터를 1건 입력하였습니다.
view.php 출력 (에러 시)
오류: SQLSTATE[23000]: Integrity constraint violation ...
용어 핵심 용어 정리
DSNData Source Name. DB 종류·호스트·DB명·문자셋을 한 줄에 담은 접속 문자열
PDOPHP Data Objects. MySQL·SQLite 등 여러 DB를 같은 API로 다루는 추상화 클래스
트랜잭션DB 작업 묶음을 하나의 단위로 처리 — 전부 성공 또는 전부 취소(원자성)
beginTransaction트랜잭션 시작 선언. 이후 commit/rollBack이 올 때까지 DB에 즉시 반영되지 않음
commit트랜잭션을 확정해 실제 DB에 영구 저장. 이 호출이 없으면 변경사항이 사라짐
rollBack에러 발생 시 beginTransaction 시점으로 되돌림. commit의 반대 동작
prepareSQL 문자열을 DB에 미리 컴파일. 자리표시자(:name)가 있는 Prepared Statement 생성
bindValue자리표시자에 실제 값을 연결. 값을 문자열로 취급해 SQL Injection을 차단함

1 DSN — 왜 한 줄에 접속 정보를 모두 담는가

$dsn은 PDO 생성자에 넘기는 연결 문자열이다. DB 종류(mysql), 호스트, DB명, 문자셋을 콜론·세미콜론으로 구분해 한 곳에 모아둔다. 나중에 DB를 SQLite나 PostgreSQL로 바꿀 때 DSN 한 줄만 수정하면 나머지 코드는 그대로 동작하도록 설계된 이유가 여기 있다.

$dsn = "mysql:host=localhost;dbname=sampledb;charset=utf8"; // ^^^^^ DB종류 ^^^^ 호스트 ^^^^^^^^ DB명 ^^^^^^^ 문자셋
※ charset=utf8mb4를 쓰면 이모지(4바이트 문자)도 저장 가능. utf8은 MySQL에서 3바이트까지만 처리한다.

2 setAttribute — 왜 두 번 호출하는가

PDO 객체를 생성하면 기본적으로 에러가 발생해도 조용히 넘어간다. setAttribute로 두 가지 설정을 해야 에러를 제대로 잡을 수 있다.

설정 왜 필요한가
ATTR_ERRMODE ERRMODE_EXCEPTION 에러 발생 시 PDOException을 던짐 → catch 블록에서 처리 가능
ATTR_EMULATE_PREPARES false PDO가 Prepared Statement를 에뮬레이션 안 함 → MySQL 드라이버가 직접 바인딩 처리
⚠ EMULATE_PREPARES를 true(기본값)로 두면 PDO가 바인딩을 에뮬레이션하는데, 이 경우 PARAM_INT 처리 등이 부정확해질 수 있다. false로 명시하는 습관을 들이자.

3 beginTransaction — 왜 INSERT 전에 먼저 호출하는가

트랜잭션 없이 execute()를 호출하면 DB에 즉시 반영(auto-commit)된다. 에러가 나도 이미 들어간 데이터를 되돌릴 수 없다. beginTransaction()을 먼저 호출하면 "지금부터 내가 명시적으로 commit할 때까지 DB에 잠궈두라"는 신호를 보낸다.

성공 경로 — commit으로 확정
beginTransaction()
prepare + bindValue
execute()
commit()
실패 경로 — rollBack으로 원상복구
beginTransaction()
execute()
PDOException 발생
rollBack()
※ 정보처리산업기사 실기에도 commit 누락 문제가 출제된다. Oracle·JSP에서도 commit()을 명시해야 DB에 반영된다.

 

반응형

 

4 prepare + bindValue — 왜 SQL에 값을 직접 이어붙이지 않는가

가장 흔한 실수는 "INSERT INTO member VALUES ('".$_POST['name']."')"처럼 POST 값을 SQL 문자열에 직접 합치는 것이다. 사용자가 ' OR '1'='1을 입력하면 의도치 않은 쿼리가 실행된다(SQL Injection). prepare() + bindValue()는 이를 구조적으로 차단한다.

위험한 방식 — 직접 연결
"VALUES ('".$_POST['last_name']."')" · $_POST 값이 SQL 구조의 일부가 됨
· 악의적 입력으로 쿼리 조작 가능
· SQL Injection 취약점 발생
안전한 방식 — bindValue
VALUES (:last_name, :first_name, :age) · SQL 구조(자리표시자)와 값을 분리
· 값이 아무리 이상해도 SQL 구조 변경 불가
· 타입(PARAM_STR/INT)도 명시적으로 지정
PARAM_STR vs PARAM_INT — 왜 타입을 지정하는가
상수 용도 예시
PDO::PARAM_STR 문자열 데이터 이름, 성 — 문자열로 이스케이프
PDO::PARAM_INT 정수 데이터 나이(age) — 숫자로 형변환 후 바인딩
PDO::PARAM_BOOL 불리언(0/1) 활성화 플래그 등
PDO::PARAM_NULL NULL 값 선택 입력 필드가 비어있을 때

5 execute — 왜 바인딩 이후에야 실행하는가

execute()는 바인딩된 값을 들고 DB 서버에 SQL을 전송하는 메서드다. prepare()로 컴파일된 SQL 구조와 bindValue()로 연결된 값이 이 시점에 합쳐진다. 트랜잭션이 열려 있으므로 실행은 되지만 아직 DB에 영구 반영되지 않은 상태다.

1
prepare($sql)SQL 구조를 DB에 미리 컴파일해 $stmh(statement handle) 객체 반환
2
bindValue(자리표시자, 값, 타입)컴파일된 구조의 각 :name 위치에 POST 값을 타입 지정해 연결
3
execute()바인딩 완료된 쿼리를 DB 서버에 전송 — 처리는 되지만 commit 전까지 임시 상태
4
commit()트랜잭션 확정 — 이 시점에 MySQL 테이블에 row가 실제로 추가됨

6 rowCount — 처리 결과를 왜 행(row) 수로 세는가

$stmh->rowCount()는 실행된 SQL이 영향을 준 행(row) 수를 반환한다. INSERT 1건이면 1을 반환한다. "결과를 센다(count) = 행(row)의 수"라는 의미이며 PHP·JSP·ASP 모두 같은 패턴으로 제공된다. DB의 자료는 행 단위로 저장되기 때문에 처리 결과를 행 수로 표현하는 것이 자연스럽다.

print "데이터를 " . $stmh->rowCount() . "건 입력하였습니다."; // execute() 이후 $stmh에는 처리 결과가 담긴 rs(ResultSet) 객체가 있음 // INSERT 1건 → "데이터를 1건 입력하였습니다."
⚠ rowCount()는 INSERT/UPDATE/DELETE에서 정확히 작동한다. SELECT 결과의 행 수를 세는 용도로는 사용하지 말 것 — 드라이버에 따라 -1을 반환하기도 한다.

7 rollBack — 에러 시 왜 먼저 되돌려야 하는가

INSERT 도중 에러가 나면 catch 블록으로 이동한다. 트랜잭션이 열린 채로 에러가 발생하면 DB는 중간 상태가 된다. rollBack()beginTransaction() 시점으로 DB를 되돌려 "반쪽짜리 INSERT"가 남지 않도록 보장한다.

catch (PDOException $e) { $pdo->rollBack(); // 먼저 되돌리고 print "오류: " . $e->getMessage(); // 그 다음 에러 메시지 출력 }
※ rollBack() 없이 PHP 프로세스가 끝나도 커넥션이 닫히면서 자동 rollBack되긴 한다. 하지만 명시적으로 쓰는 것이 의도를 명확히 하고, persistent connection 사용 시 예기치 않은 상황을 방지한다.

8 자주 하는 실수
실수 증상 해결
commit() 누락 execute()까지 에러 없이 완료됐는데 MySQL에서 SELECT해도 데이터가 없음 execute() 이후 반드시 $pdo->commit() 호출
beginTransaction() 없이 execute() auto-commit으로 동작 — 에러 발생 시 되돌릴 수 없음 execute() 전에 beginTransaction()을 먼저 호출
bindValue 자리표시자 철자 불일치 SQLSTATE HY093: invalid parameter number 오류 SQL의 :last_name과 bindValue(':last_name') 철자 완전 일치 확인
PARAM_STR을 정수 컬럼에 사용 대부분 동작하지만 DB 타입 캐스팅 오버헤드 발생 정수 컬럼에는 PDO::PARAM_INT 명시
prepare() 없이 execute() 직접 호출 Fatal Error: Call to a member function execute() on bool $stmh = $pdo->prepare($sql) 후 $stmh->execute() 순서 지킬 것

핵심 한 줄 요약

DSNDB 종류·호스트·DB명·문자셋을 한 줄에 — 나중에 DB 교체도 이 한 줄만
prepare + bindValue값을 SQL에 직접 이어붙이지 않는다 — SQL Injection을 구조적으로 차단
beginTransaction → execute트랜잭션을 열고 실행 — commit 전까지 DB에 반영되지 않음
commit()이 호출이 없으면 INSERT는 사라진다 — 정보처리산업기사에도 나오는 함정
rollBack()에러 시 beginTransaction 시점으로 되돌림 — 반쪽 INSERT를 방지
rowCount()INSERT/UPDATE/DELETE가 영향을 준 행 수 — PHP·JSP·ASP 공통 패턴

Tags

#PDO #PHP #MySQL #INSERT #beginTransaction #commit #rollBack #prepare #bindValue #execute #트랜잭션 #PHP학습 #웹개발 #티스토리
▼ 티스토리 태그 입력란 복사용
PDO, PHP, MySQL, INSERT, beginTransaction, commit, rollBack, prepare, bindValue, 트랜잭션
반응형

댓글