데이터베이스에서 조회한 결과를 배열로 받을 때 mysqli_fetch_array()를 쓰는 개발자가 많다. 다만 대부분은 세 번째 매개변수(fetch_mode)가 뭔지, 왜 있는지 모르고 그냥 기본값으로 방치한다. 결과적으로 배열에서 값을 꺼낼 때 숫자 인덱스로도, 컬럼명으로도 접근할 수 있어서 혼동하거나, 불필요한 메모리를 낭비하거나, 코드의 의도가 불명확해진다. 이번에는 fetch_mode가 정확히 뭔지, 어떻게 다른지, 실무에서 어떻게 써야 하는지 완벽하게 정리해서 소개하겠다.
mysqli_fetch_array()는 SELECT 쿼리 결과의 한 행을 배열로 반환한다. 이때 배열의 구성 방식이 fetch_mode에 따라 달라진다. fetch_mode는 3가지가 있다.
| fetch_mode | 반환 배열 | 메모리 사용 | 용도 |
|---|---|---|---|
| MYSQLI_ASSOC | 컬럼명을 키로 사용 (연관 배열) | 적음 | 컬럼명으로 접근, 권장 |
| MYSQLI_NUM | 숫자 인덱스만 사용 | 적음 | 컬럼 순서 알 때만 사용 |
| MYSQLI_BOTH (기본값) | 컬럼명 + 숫자 인덱스 모두 | 많음 | 피해야 할 선택 |
기본값이 MYSQLI_BOTH라서 아무 지정 안 하면 각 컬럼마다 2개의 키(컬럼명 1개 + 숫자 1개)가 생긴다. 메모리는 2배가 되고, 코드에서 어느 방식으로 접근하는지 불명확해진다.
컬럼명을 배열의 키로 사용한다. 가독성이 좋고 코드 의도가 명확하다.
<?php
$result = $mysqli->query("SELECT id, name, email FROM users LIMIT 1");
$row = $result->fetch_array(MYSQLI_ASSOC);
// 반환된 배열 구조
// Array ( [id] => 1 [name] => John [email] => john@example.com )
echo $row['name']; // ✓ John
echo $row['email']; // ✓ john@example.com
echo $row[0]; // ✗ 에러 또는 원치 않은 동작
?>
각 컬럼을 숫자 인덱스로만 접근한다. 컬럼 순서를 정확히 알고 있을 때만 사용한다.
<?php
$result = $mysqli->query("SELECT id, name, email FROM users LIMIT 1");
$row = $result->fetch_array(MYSQLI_NUM);
// 반환된 배열 구조
// Array ( [0] => 1 [1] => John [2] => john@example.com )
echo $row[0]; // ✓ 1 (id)
echo $row[1]; // ✓ John (name)
echo $row[2]; // ✓ john@example.com (email)
echo $row['name']; // ✗ 에러 또는 원치 않은 동작
?>
컬럼명과 숫자 인덱스 모두 포함한다. 메모리 낭비이고 코드 의도가 불명확하다.
<?php
$result = $mysqli->query("SELECT id, name, email FROM users LIMIT 1");
$row = $result->fetch_array(); // fetch_mode 생략, 기본값은 MYSQLI_BOTH
// 반환된 배열 구조
// Array (
// [0] => 1 [id] => 1
// [1] => John [name] => John
// [2] => john@example.com [email] => john@example.com
// )
echo $row['name']; // ✓ John
echo $row[1]; // ✓ John (중복)
echo sizeof($row); // 6 (3개 컬럼 × 2개 키)
?>
<?php
// ✗ 잘못된 코드
$result = $mysqli->query("SELECT id, name, email, phone FROM users LIMIT 1000");
while ($row = $result->fetch_array()) { // 기본값 MYSQLI_BOTH
// 각 행마다 8개 키 (4 컬럼 × 2)
// 1000행 × 8키 = 불필요한 메모리 소비
echo $row['name'];
}
// ✓ 올바른 코드
$result = $mysqli->query("SELECT id, name, email, phone FROM users LIMIT 1000");
while ($row = $result->fetch_array(MYSQLI_ASSOC)) { // 명시적 지정
// 각 행마다 4개 키
// 메모리 사용량 50% 감소
echo $row['name'];
}
?>
<?php
// PDO를 쓸 수 없을 때 mysqli 권장 패턴
function getUserList($mysqli) {
$result = $mysqli->query("SELECT id, name, email, created_at FROM users");
if (!$result) {
throw new Exception($mysqli->error);
}
$users = [];
while ($row = $result->fetch_array(MYSQLI_ASSOC)) {
$users[] = [
'id' => $row['id'],
'name' => $row['name'],
'email' => $row['email'],
'created_at' => $row['created_at']
];
}
$result->free();
return $users;
}
$users = getUserList($mysqli);
foreach ($users as $user) {
echo "ID: {$user['id']}, Name: {$user['name']}<br />";
}
?>
개발할 때는 문제 없지만 프로덕션에서 대량의 데이터를 조회할 때 메모리 부족 문제가 생긴다. PDO나 mysqli_fetch_assoc()을 쓰는 게 낫다.
<?php
// ✗ 안 함
$row = $result->fetch_array();
// ✓ 그냥 이걸 쓰세요
$row = $result->fetch_assoc();
?>
컬럼 순서가 바뀌거나 쿼리가 수정되면 인덱스가 맞지 않아 데이터를 잘못 읽는다.
<?php
// ✗ 잘못된 코드
$result = $mysqli->query("SELECT id, name, email FROM users");
while ($row = $result->fetch_array(MYSQLI_NUM)) {
echo $row['email']; // ✗ 에러: 인덱스 없음
}
// ✓ 올바른 코드
while ($row = $result->fetch_array(MYSQLI_NUM)) {
echo $row[2]; // ✓ email은 3번째 컬럼 (인덱스 2)
}
?>
직접 fetch_array()를 쓰기보다는 다른 방법을 권장한다.
<?php
// 권장 1: PDO (최신, 표준)
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
$stmt = $pdo->query("SELECT id, name FROM users");
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
echo $row['name'];
}
// 권장 2: mysqli_fetch_assoc()
$result = $mysqli->query("SELECT id, name FROM users");
while ($row = $result->fetch_assoc()) {
echo $row['name'];
}
// 권장 3: mysqli fetch_array(MYSQLI_ASSOC)
$result = $mysqli->query("SELECT id, name FROM users");
while ($row = $result->fetch_array(MYSQLI_ASSOC)) {
echo $row['name'];
}
?>
mysqli_fetch_array()의 fetch_mode는 반환 배열의 키 방식을 결정한다. MYSQLI_ASSOC를 명시적으로 지정하면 메모리를 절약하고 코드 의도를 명확하게 하며 버그를 줄인다. 기본값 MYSQLI_BOTH는 대량 데이터 조회에서 성능 문제를 일으키므로 절대 쓰지 말자. 하지만 이왕이면 PDO나 mysqli_fetch_assoc()처럼 더 직관적인 함수를 쓰는 게 좋다. 이 글의 권장사항을 참고해 지금 당신의 프로젝트에서 쓰고 있는 mysqli_fetch_array() 호출을 모두 명시적인 fetch_mode로 바꾸거나, PDO로 마이그레이션하면 메모리 효율성과 코드 품질이 함께 개선될 것이다.