PHP에서 MariaDB를 다루는 방법: PDO와 MySQLi의 비교와 범용 클래스 활용
PHP로 웹 애플리케이션을 개발할 때 데이터 저장과 조회는 필수적인 과정입니다. 특히 MariaDB는 MySQL과 완벽한 호환을 유지하면서도 성능과 기능 면에서 많은 장점을 제공하므로 국내외에서 널리 사용되고 있습니다. 본문에서는 PHP의 대표적인 두 확장 모듈인 PDO와 MySQLi를 비교·분석하고, 이를 응용한 범용 PHP 클래스인 MariaDBHandler를 소개합니다. 실제 코드 예제와 보안·성능 팁까지 함께 다루어, 여러분의 프로젝트에 바로 적용 가능한 실전 가이드를 제공합니다.
1. 들어가며
데이터베이스를 다루는 방법은 프로젝트의 유지보수성과 확장성을 결정짓는 핵심 요소입니다. 어떤 라이브러리를 선택하느냐에 따라 보안 수준, 코드 가독성, 이식성, 성능 등에 큰 차이가 발생합니다. PHP에서는 오래전부터 MySQL 확장 함수(mysql_*)를 사용해왔지만, PHP 7 이후 해당 함수는 제거되었고 현재는 PDO와 MySQLi만이 공식적으로 권장됩니다.
MariaDB는 빠른 트랜잭션 처리와 유연한 스토리지 엔진 지원, 커뮤니티 중심 개발 방식 등으로 많은 기업과 개발자가 선택하는 데이터베이스입니다. 그렇다면 PHP에서 MariaDB를 가장 효율적이고 안전하게 다룰 방법은 무엇일까요? 바로 PDO와 MySQLi의 장단점을 이해한 뒤, 적절한 방식을 선택하거나 공통 인터페이스를 감싸는 범용 클래스를 활용하는 것입니다.
2. PDO vs MySQLi: 어떤 걸 선택할까?
PDO와 MySQLi는 모두 PHP에서 MySQL/MariaDB에 접속하고 쿼리를 실행하는 기능을 제공합니다. 하지만 지원하는 기능과 사용 편의성 면에서는 분명한 차이가 있습니다. 다음은 두 확장 모듈의 주요 특징을 요약한 리스트입니다.
- DBMS 지원 범위: PDO는 MySQL, MariaDB 외에도 PostgreSQL, SQLite, SQL Server 등을 지원합니다. MySQLi는 오직 MySQL/MariaDB만 지원합니다.
- API 스타일: PDO는 객체 지향 방식만 제공하는 반면, MySQLi는 절차형과 객체 지향 방식을 모두 지원합니다.
- 바인딩 파라미터: PDO는 named placeholder(
:email) 방식을 사용해 가독성과 유지보수성을 높입니다. MySQLi는 순차형 placeholder(?)만 지원합니다. - Prepared Statement 처리: MySQLi는 서버 측 네이티브 prepared statement를 사용합니다. PDO는 기본적으로 클라이언트 측 에뮬레이션을 사용하나,
PDO::ATTR_EMULATE_PREPARES설정으로 서버 측 prepared statement를 활성화할 수 있습니다. - 트랜잭션: 두 모듈 모두 트랜잭션 처리를 지원하며, PDO는
beginTransaction,commit,rollBack을 통해 예외적인 상황까지 깔끔하게 관리할 수 있습니다. - 성능: 벤치마크에 따라 차이가 있지만 일반적으로 MySQLi가 5~10% 정도 속도 우위를 보이는 경우가 있습니다. 그러나 대다수 웹 애플리케이션에서는 해당 차이를 체감하기 어렵습니다.
2-1. 지원 DBMS와 이식성
프로젝트가 단일 데이터베이스(MySQL/MariaDB)에만 의존한다면 MySQLi가 충분합니다. 반면 PostgreSQL, SQLite, Oracle 등 다양한 DBMS를 병행하거나 나중에 전환할 가능성이 있다면 PDO가 훨씬 유리합니다. 하나의 통일된 인터페이스로 여러 드라이버를 교체할 수 있기 때문입니다.
| 구분 | 지원 DBMS | 이식성 |
|---|---|---|
| PDO | MySQL, MariaDB, PostgreSQL, SQLite, SQL Server 등 다수 | 동일 인터페이스로 드라이버 교체 가능 |
| MySQLi | MySQL, MariaDB | MySQL 계열에만 최적화, 다른 DB 사용 불가 |
2-2. 코드 스타일과 가독성
MySQLi는 절차형 함수와 객체 지향 메서드를 병행할 수 있어 편리하지만, 일관된 객체 지향 개발을 추구한다면 PDO가 더 깔끔합니다. 특히 named placeholder를 사용하면 파라미터 관리가 명확해지고, SQL과 파라미터 배열의 대응 관계를 직관적으로 파악할 수 있습니다.
2-3. 보안과 SQL Injection 방지
두 방식 모두 prepared statement를 통해 SQL Injection을 효과적으로 방지할 수 있습니다. 다만 PDO는 에뮬레이션 모드를 끌 수 있고, 파라미터 타입을 명시적으로 설정할 수 있어 더욱 일관성 있는 보안 처리가 가능합니다. MySQLi도 bind_param 메서드를 통해 타입을 지정할 수 있지만, named placeholder가 제공되지 않으므로 대규모 프로젝트에서 혼동이 발생할 여지가 있습니다.
2-4. 성능 비교
실제로 간단한 벤치마크를 진행해 보면 MySQLi가 PDO보다 5~10% 정도 빠르게 동작하는 사례를 확인할 수 있었습니다. 그러나 애플리케이션 레벨에서 네트워크 지연, 비즈니스 로직, 렌더링 등 다른 요소들이 복합적으로 작용하므로, 단순 쿼리 속도의 차이가 전체 성능에 미치는 영향은 크지 않습니다.
| 비교 항목 | MySQLi | PDO |
|---|---|---|
| 평균 실행 속도 | 약 5~10% 더 빠름 | 추상화 계층으로 약간 느림 |
| 메모리 사용량 | 더 낮음 | 약간 높음 |
| Prepared Statement | 서버 측 네이티브 지원 | 기본 클라이언트 측 에뮬레이션 |
| 대량 처리 성능 | 더 우수 | 약간 느릴 수 있음 |
| 실제 차이 체감 | 거의 없음 | 거의 없음 |
3. PDO에 대한 오해와 진실
“PDO로는 일반 SQL을 쓸 수 없다”는 이야기가 종종 들리지만, 이는 잘못된 정보입니다. PDO는 prepare()와 execute() 방식을 권장하지만, query()와 exec() 메서드를 통해 일반 SQL도 그대로 실행할 수 있습니다.
$pdo = new PDO($dsn, $user, $pass);
$sql = "SELECT * FROM users WHERE status = 'active' ORDER BY created_at DESC";
$stmt = $pdo->query($sql);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
$pdo->exec("DELETE FROM logs WHERE created_at < NOW() - INTERVAL 7 DAY");
따라서 개발자 필요에 따라 안전성과 가독성을 위해 prepare()/execute()를 사용하거나, 단발성 쿼리에는 query()/exec()를 사용해도 무방합니다.
4. 범용 MariaDBHandler 클래스 소개
매번 비슷한 코드로 PDO 연결, 쿼리 준비, 파라미터 바인딩, 결과 처리 코드를 반복하는 대신, 공통 기능만 추상화한 클래스를 만들면 생산성과 유지보수성이 크게 향상됩니다. 아래는 완전한 구현 예시입니다.
<?php
class MariaDBHandler {
private $pdo;
public function __construct($host, $dbname, $user, $pass) {
$dsn = "mysql:host={$host};dbname={$dbname};charset=utf8mb4";
try {
$this->pdo = new PDO($dsn, $user, $pass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]);
} catch (PDOException $e) {
die("DB 연결 실패: " . $e->getMessage());
}
}
public function query($sql, $params = []) {
$stmt = $this->pdo->prepare($sql);
$stmt->execute($params);
return $stmt;
}
public function fetchAll($sql, $params = []) {
return $this->query($sql, $params)->fetchAll();
}
public function fetchOne($sql, $params = []) {
return $this->query($sql, $params)->fetch();
}
public function insert($table, $data) {
$columns = implode(", ", array_keys($data));
$placeholders = ":" . implode(", :", array_keys($data));
$sql = "INSERT INTO {$table} ({$columns}) VALUES ({$placeholders})";
$this->query($sql, $data);
return $this->pdo->lastInsertId();
}
public function update($table, $data, $where) {
$setClause = implode(", ", array_map(function($col){ return "{$col} = :{$col}"; }, array_keys($data)));
$whereClause = implode(" AND ", array_map(function($col){ return "{$col} = :where_{$col}"; }, array_keys($where)));
$params = array_merge(
$data,
array_combine(
array_map(function($col){ return "where_{$col}"; }, array_keys($where)),
array_values($where)
)
);
$sql = "UPDATE {$table} SET {$setClause} WHERE {$whereClause}";
return $this->query($sql, $params);
}
public function delete($table, $where) {
$whereClause = implode(" AND ", array_map(function($col){ return "{$col} = :{$col}"; }, array_keys($where)));
$sql = "DELETE FROM {$table} WHERE {$whereClause}";
return $this->query($sql, $where);
}
}
?>
5. 사용 예제
5-1. 데이터 조회
$db = new MariaDBHandler('localhost', 'test_db', 'user', 'pass');
$activeUsers = $db->fetchAll(
"SELECT id, name, email
FROM users
WHERE status = :status
ORDER BY id DESC",
['status' => 'active']
);
5-2. 데이터 삽입
$newId = $db->insert('users', [
'name' => 'Daniel',
'email' => 'daniel@example.com',
'status' => 'active'
]);
5-3. 데이터 수정
$db->update(
'users',
['email'=>'new_email@example.com', 'status'=>'pending'],
['id' => $newId]
);
5-4. 데이터 삭제
$db->delete('users', ['id' => $newId]);
5-5. 일반 SQL과 바인딩 혼용
$sql = "
SELECT u.name, o.total
FROM users u
JOIN orders o
ON u.id = o.user_id
WHERE o.total > :min
AND u.status = 'active'
ORDER BY o.total DESC
";
$reports = $db->fetchAll($sql, ['min' => 100]);
6. 보안 및 성능 팁
- Prepared Statement 사용으로 SQL Injection을 사전에 방지하세요.
- 트랜잭션 처리(
beginTransaction,commit,rollBack)로 일관된 상태를 유지하세요. - WHERE 절과 JOIN 절에 적절한 인덱스를 설정하여 쿼리 성능을 높이세요.
- Persistent Connection 또는 커넥션 풀을 사용해 빈번한 연결/해제 오버헤드를 줄이세요.
- 쿼리 로깅과 프로파일링을 통해 자주 실행되는 쿼리를 검토하고 최적화하세요.
try {
// 트랜잭션 예시
$db->pdo->beginTransaction();
$db->insert('accounts', ['user'=>'A','balance'=>100]);
$db->insert('accounts', ['user'=>'B','balance'=>100]);
$db->pdo->commit();
} catch (Exception $e) {
$db->pdo->rollBack();
throw $e;
}
7. 마무리하며
PHP와 MariaDB 연동은 프로젝트의 기초 체력과 같습니다. PDO와 MySQLi 중 어느 것을 택할지는 성능, 확장성, 코드 일관성, 팀의 역량 등을 종합적으로 고려해야 합니다. 단일 MySQL/MariaDB 프로젝트라면 MySQLi도 훌륭한 선택이지만, 다양한 DBMS 지원과 예외 처리, 가독성 측면에서 PDO를 선택하면 장기적으로 더 유리할 수 있습니다.
MariaDBHandler와 같은 범용 클래스를 도입하면 매번 중복되는 연결 및 쿼리 코드를 줄일 수 있으며, 유지보수가 쉬워지고 코드 품질도 높아집니다. 본문에서 소개한 예제와 팁을 참고하여, 여러분의 PHP 프로젝트에 안전하고 효율적인 데이터베이스 계층을 구축해 보시기 바랍니다.
지금 바로 코드를 적용해 보시고, 성능과 보안, 유지보수성 측면에서 차이를 경험해 보세요!
이 콘텐츠는 스마트베이(SmartBay)에서 제공하며, 실무 중심의 정보 전달을 목표로 구성되었습니다. 스마트베이는 다양한 개발자 경험을 바탕으로 실용적인 IT 정보를 지속적으로 소개합니다
비즈니스 효율을 높이는 스마트한 IT 솔루션을 제공합니다.
웹사이트 : www.esmartbay.co.kr
E-mail : smartbay.svc@gmail.com
카카오톡 : 바로 상담하기
'프로그래밍 > PHP' 카테고리의 다른 글
| PHP 패키지 관리의 기준, Composer 입문부터 실전까지 (5) | 2025.07.12 |
|---|---|
| PHP로 클라이언트 IP 정확하게 추출하고 사용자 위치 분석하기 (3) | 2025.06.15 |
| 2025년에 PHP는 여전히 유용한가? 최신 동향과 생존 전략 정리 (2) | 2025.06.09 |