시작 오늘 분석할 코드
PDO(PHP Data Objects)는 PHP 5부터 등장한 데이터베이스 연결 클래스다. mysqli와 달리 MySQL뿐 아니라 다양한 DB를 하나의 인터페이스로 다룰 수 있다. 접속 방식, DSN 문자열, 종료 방법이 mysqli와 다르다.
$pdo = new PDO( 'mysql:host=localhost;dbname=sampledb;charset=utf8', 'root', // DB 사용자명 '' // DB 비밀번호 (XAMPP 기본: 빈 문자열) ); print 'PDO 클래스를 통해 접속이 성공하였습니다.'; // DB 관련 처리를 여기에서 진행 $pdo = null; // PDO 객체에 null 대입 → 접속 종료
실제 출력 미리보기
PDO 클래스를 통해 접속이 성공하였습니다.
→ new PDO() 접속 성공 시 print 문 실행. 실패 시 PDOException이 발생해 이후 코드는 실행 안 됨
용어 핵심 용어 정리
PDOPHP Data Objects. PHP 5에서 등장한 다중 DB 지원 클래스. MySQL, SQLite, PostgreSQL 등 지원
DSNData Source Name. DB 접속 정보를 담은 문자열. 'mysql:host=...;dbname=...;charset=...' 형식
charset=utf8DSN에 포함하는 문자셋 지정. 한글 데이터 처리 시 반드시 지정 필요
$pdo = nullPDO 객체에 null 대입 → 객체 소멸 → DB 접속 자동 종료. close() 없이 이 방식 사용
PDOExceptionPDO 접속/쿼리 실패 시 발생하는 예외 클래스. try/catch로 처리
이식성DB 종류가 바뀌어도 DSN만 변경하면 나머지 코드를 그대로 쓸 수 있는 PDO의 장점
PHP5 이후PDO는 PHP 5.1부터 내장. 현대 PHP 프로젝트에서 PDO 또는 ORM 사용이 일반적
dbnameDSN에서 접속할 데이터베이스 이름 지정. mysqli의 네 번째 인수와 동일한 역할
1 PDO 등장 배경 — 왜 PDO인가
PHP는 역사가 길어 DB 접속 함수가 여럿이다
PHP 초기에는 mysql_connect() 같은 전통적인 함수들이 있었고, 이후 객체지향 방식의 mysqli가 추가됐다. 하지만 둘 다 MySQL 전용이었다. PDO는 다양한 DB를 하나의 인터페이스로 다룰 수 있어 코드 이식성이 높다. 최신 PHP 프로젝트에서는 PDO 또는 그 위에서 동작하는 ORM(Eloquent 등)을 많이 사용한다.
이전 mysql_* 함수
mysql_connect() 방식
절차적 스타일
PHP 7에서 완전 제거
MySQL 전용
PDO (현대적)
new PDO() 객체지향
12개 이상 DB 지원
PHP 5.1+ 내장
try/catch 예외 처리
2 DSN 문자열 해부
첫 번째 인수가 모든 접속 정보를 담고 있다
PDO의 첫 번째 인수 'mysql:host=localhost;dbname=sampledb;charset=utf8'는 DSN이다. mysqli는 인수 4개를 따로 받는 반면, PDO는 DB 종류부터 호스트·DB명·문자셋까지 문자열 하나에 담는다. mysql: 부분이 드라이버 이름이며, 다른 DB를 쓸 때는 이 부분만 바꾸면 된다.
// DSN 분해 'mysql' // 드라이버명: MySQL 사용 ':host=localhost' // MySQL 서버 주소 ';dbname=sampledb' // 접속할 DB 이름 ';charset=utf8' // 문자셋 (한글 처리 위해 필수)
| 드라이버 |
DSN 예시 |
지원 DB |
| mysql |
mysql:host=localhost;dbname=test |
MySQL, MariaDB |
| sqlite |
sqlite:/path/to/database.db |
SQLite (파일 기반) |
| pgsql |
pgsql:host=localhost;dbname=test |
PostgreSQL |
| sqlsrv |
sqlsrv:Server=localhost;Database=test |
Microsoft SQL Server |
※ PDO의 이식성 핵심: DB가 MySQL에서 PostgreSQL로 바뀌어도 DSN만 바꾸면 prepare/execute 등 나머지 코드는 그대로 동작한다.
3 $pdo = null — 접속 종료 방식이 다른 이유
mysqli의 close()와 PDO의 null 대입
mysqli는 $mysqli->close() 메서드로 명시적으로 닫지만, PDO는 $pdo = null로 객체에 null을 대입해 접속을 종료한다. PDO 객체를 null로 만들면 PHP가 해당 객체를 소멸시키고, 이 과정에서 DB 접속도 자동으로 끊어진다.
// mysqli 방식 $mysqli->close(); // 메서드 호출로 명시적 종료 // PDO 방식 $pdo = null; // null 대입 → 객체 소멸 → 접속 자동 종료 // 변수 자체를 삭제하는 방법도 동일한 효과 unset($pdo);
⚠ $pdo = null로 대입하면 DB 접속은 종료되지만 변수 $pdo는 null로 남는다. unset($pdo)는 변수까지 삭제한다. 둘 다 접속 종료 효과는 동일하다.
4 PDO 접속 옵션 배열 — 생성자 네 번째 인수
setAttribute를 생성자에서 한 번에 설정하는 방법
PDO 생성자의 네 번째 인수로 옵션 배열을 전달하면 setAttribute()를 별도로 여러 번 호출할 필요가 없다. 특히 에러 모드와 emulate prepares 설정을 초기화 시점에 한 번에 적용할 수 있어 코드가 깔끔해진다.
$options = [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_EMULATE_PREPARES => false, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]; $pdo = new PDO( 'mysql:host=localhost;dbname=sampledb;charset=utf8mb4', 'root', '', $options // ← 네 번째 인수로 옵션 배열 전달 );
| 옵션 상수 |
값 |
의미 |
| ATTR_ERRMODE |
ERRMODE_EXCEPTION |
에러 시 PDOException throw — try/catch 필수 |
| ATTR_EMULATE_PREPARES |
false |
MySQL이 직접 prepared statement 처리 — 보안 향상 |
| ATTR_DEFAULT_FETCH_MODE |
FETCH_ASSOC |
fetch() 기본 반환 형식을 연관배열로 지정 |
실제 프로젝트에서는 위 세 옵션을 생성자에 배열로 한 번에 전달하는 패턴이 일반적이다. setAttribute를 나중에 여러 번 호출하는 것보다 관리가 편하다.
5 PDO 기본 쿼리 실행 — query()와 fetch()
접속 후 데이터를 꺼내는 가장 간단한 방법
에러 처리(try/catch + setAttribute)가 없는 기본 코드에서도 query()와 fetchAll()로 결과를 꺼낼 수 있다. 이 패턴은 학습용이고, 운영 환경에서는 try/catch + prepared statement를 사용한다.
$pdo = new PDO('mysql:host=localhost;dbname=sampledb;charset=utf8', 'root', ''); $stmt = $pdo->query("SELECT * FROM member"); // 외부 입력값 없는 간단한 쿼리는 query()로 바로 실행 foreach($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) { echo $row['id'] . ' ' . $row['last_name'] . "<br>"; } $pdo = null; // 접속 종료
⚠ $pdo->query()는 외부 입력값(사용자 입력, $_POST 등)을 SQL에 직접 넣을 경우 SQL Injection에 취약하다. 외부 값이 있으면 반드시 prepare + bindValue 방식 사용.
6 PDO 기본 에러 동작과 위험성
setAttribute 없으면 에러가 조용히 무시된다
dbtest3처럼 setAttribute로 에러 모드를 설정하지 않으면, PDO는 기본적으로 ERRMODE_SILENT 상태다. 이 상태에서는 쿼리가 실패해도 아무런 경고 없이 코드가 계속 실행된다. 학습용 코드에서는 잘 보이지 않지만, 실제 서비스에서는 데이터가 저장 안 됐는데 성공한 것처럼 보이는 문제가 생길 수 있다.
| 에러 모드 |
동작 |
권장 여부 |
| ERRMODE_SILENT (기본) |
에러 무시, 코드 계속 실행 |
❌ 사용 금지 |
| ERRMODE_WARNING |
PHP Warning 발생, 실행 계속 |
△ 개발 중 임시 |
| ERRMODE_EXCEPTION |
PDOException throw, try/catch로 처리 |
✅ 항상 이걸 사용 |
// dbtest3처럼 에러 모드 없이 접속 — 학습용 코드에서만 허용 $pdo = new PDO('mysql:host=localhost;dbname=sampledb;charset=utf8', 'root', ''); // 이 상태에서 $pdo->query("틀린 SQL") 실행해도 false 반환만 됨 — 에러 없음 // 실무에서는 반드시 setAttribute 추가 $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
⚠ ERRMODE_SILENT 상태에서는 잘못된 SQL도 에러 없이 통과한다. 데이터 저장 실패를 감지할 수 없어 운영 환경에서 심각한 문제가 될 수 있다.
7 PDO lastInsertId() — 삽입 후 생성된 id 확인
INSERT 후 AUTO_INCREMENT id를 PHP에서 읽는 방법
PDO로 INSERT를 실행한 후 $pdo->lastInsertId()를 호출하면 방금 삽입된 레코드에 부여된 AUTO_INCREMENT id를 가져올 수 있다. mysqli의 insert_id와 동일한 역할이다.
$stmt = $pdo->prepare("INSERT INTO member (last_name, first_name, age) VALUES (:ln, :fn, :age)"); $stmt->execute([':ln'=>'Kim', ':fn'=>'Minjun', ':age'=>25]); echo $pdo->lastInsertId(); // 방금 삽입된 레코드의 id 반환 (예: 6)
lastInsertId()는 $pdo 객체(연결)에서 호출한다. $stmt(statement) 객체에는 없다. 헷갈리지 않도록 주의.
charset=utf8 vs charset=utf8mb4
dbtest3의 DSN은 charset=utf8을 사용하는데, 이모지·4바이트 문자를 저장하려면 charset=utf8mb4로 바꾸는 것이 좋다. DSN의 charset과 DB 생성 시 설정한 CHARACTER SET이 일치해야 한다.
// ❌ utf8 — 이모지 저장 불가 (3바이트 UTF-8) 'mysql:host=localhost;dbname=sampledb;charset=utf8' // ✅ utf8mb4 — 이모지 포함 전체 유니코드 지원 (4바이트) 'mysql:host=localhost;dbname=sampledb;charset=utf8mb4'
8 PDO 주요 메서드 레퍼런스
$pdo 객체에서 자주 쓰는 메서드 목록
| 메서드 |
의미 |
반환값 |
$pdo->query($sql) |
단순 쿼리 실행 (외부 입력 없을 때) |
PDOStatement |
$pdo->prepare($sql) |
prepared statement 준비 |
PDOStatement |
$pdo->exec($sql) |
결과셋 없는 SQL (INSERT/UPDATE) |
영향받은 행 수 |
$pdo->lastInsertId() |
마지막 INSERT id |
문자열(정수값) |
$pdo->setAttribute() |
PDO 속성 설정 |
bool |
$stmt->fetch(PDO::FETCH_ASSOC) |
행 하나를 연관배열로 |
배열 또는 false |
$stmt->fetchAll(PDO::FETCH_ASSOC) |
전체 결과를 배열로 |
2차원 배열 |
$stmt->rowCount() |
영향받은 행 수 (DML용) |
정수 |
9 mysqli vs PDO — 접속 코드 비교
같은 작업을 두 방식으로 비교
// ① mysqli 방식 $mysqli = new mysqli('localhost', 'root', '', 'sampledb'); if($mysqli->connect_error) { die($mysqli->connect_error); } $mysqli->close(); // ② PDO 방식 $pdo = new PDO('mysql:host=localhost;dbname=sampledb;charset=utf8', 'root', ''); // 에러: 예외(PDOException) 방식 — try/catch로 처리 (dbtest4에서 다룸) $pdo = null;
5 자주 하는 실수
| 실수 |
증상 |
해결 |
| DSN에 charset 누락 |
한글이 ??? 또는 깨진 문자로 저장됨 |
DSN에 ;charset=utf8 또는 charset=utf8mb4 추가 |
| new PDO() 실패 시 try/catch 없음 |
Uncaught PDOException: 오류 메시지 노출 |
try/catch(PDOException $e)로 감싸서 에러 처리 |
| $pdo = null 누락 |
기능상 문제는 없으나 접속이 스크립트 종료까지 유지 |
DB 작업 완료 후 $pdo = null 또는 unset($pdo) 호출 |
| DSN 세미콜론 구분자 오타 |
could not find driver 오류 |
mysql:host=...;dbname=...;charset=... 형식에서 세미콜론(;)으로 구분 |
| mysqli close()를 PDO에 사용 |
Call to undefined method PDO::close() |
PDO는 close() 메서드 없음. $pdo = null 사용 |
핵심 한 줄 요약
new PDO(DSN, user, password)DSN 한 문자열에 드라이버·호스트·DB명·charset 모두 포함
DSN = 'mysql:host=...;dbname=...;charset=utf8mb4'드라이버 변경 시 DSN만 바꾸면 나머지 코드 재사용 가능
$pdo = nullPDO 접속 종료 방식 — close() 아님. null 대입으로 객체 소멸
ATTR_ERRMODE_EXCEPTION (필수)기본값 SILENT는 에러를 조용히 무시 — 반드시 setAttribute로 변경
lastInsertId()INSERT 후 생성된 AUTO_INCREMENT id 확인 — $pdo 객체에서 호출
PDO vs mysqliPDO는 다중 DB 이식성, mysqli는 MySQL 전용 — 현대 프로젝트는 PDO 권장
query() vs prepare()외부 입력 없으면 query(), 있으면 반드시 prepare+bindValue — 보안 핵심
$stmt->rowCount()DML 후 영향받은 행 수 확인 — SELECT 결과 수는 fetchAll 후 count()
Tags
#PHP PDO #PDO 접속 #DSN 문자열 #new PDO #PHP DB 연동 #PDO MySQL #PDO null 종료 #mysqli vs PDO #PHP5 데이터베이스 #PHP 객체지향 DB #웹개발 #티스토리
▼ 티스토리 태그 입력란 복사용
PHP PDO, PDO 접속, DSN 문자열, new PDO, PHP DB 연동, PDO MySQL, PDO null 종료, mysqli vs PDO, PHP5 데이터베이스, PHP 객체지향 DB
댓글