시작 오늘 분석할 코드
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(); }
실제 출력 미리보기
→
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, 트랜잭션
댓글