데이터베이스에서 조회한 결과를 배열로 받을 때 mysqli_fetch_array()를 쓰는 개발자가 많다. 다만 대부분은 세 번째 매개변수(fetch_mode)가 뭔지, 왜 있는지 모르고 그냥 기본값으로 방치한다. 결과적으로 배열에서 값을 꺼낼 때 숫자 인덱스로도, 컬럼명으로도 접근할 수 있어서 혼동하거나, 불필요한 메모리를 낭비하거나, 코드의 의도가 불명확해진다. 이번에는 fetch_mode가 정확히 뭔지, 어떻게 다른지, 실무에서 어떻게 써야 하는지 완벽하게 정리해서 소개하겠다.

 

1단계. 기초 개념 이해하기: 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배가 되고, 코드에서 어느 방식으로 접근하는지 불명확해진다.

 

2단계. 상세 방법: 세 가지 fetch_mode 비교
MYSQLI_ASSOC: 컬럼명 키 (권장)

컬럼명을 배열의 키로 사용한다. 가독성이 좋고 코드 의도가 명확하다.

<?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];       // ✗ 에러 또는 원치 않은 동작
?>
MYSQLI_NUM: 숫자 인덱스만

각 컬럼을 숫자 인덱스로만 접근한다. 컬럼 순서를 정확히 알고 있을 때만 사용한다.

<?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']; // ✗ 에러 또는 원치 않은 동작
?>
MYSQLI_BOTH: 둘 다 (기본값, 비추천)

컬럼명과 숫자 인덱스 모두 포함한다. 메모리 낭비이고 코드 의도가 불명확하다.

<?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개 키)
?>

 

3단계. 실전 예제: 올바른 사용 패턴
문제: 기본값 MYSQLI_BOTH로 인한 메모리 낭비
<?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 />";
}
?>

 

4단계. 주의사항과 흔한 실수
✗ 실수 1: fetch_mode 생략하고 MYSQLI_BOTH 사용

개발할 때는 문제 없지만 프로덕션에서 대량의 데이터를 조회할 때 메모리 부족 문제가 생긴다. PDO나 mysqli_fetch_assoc()을 쓰는 게 낫다.

<?php
// ✗ 안 함
$row = $result->fetch_array();

// ✓ 그냥 이걸 쓰세요
$row = $result->fetch_assoc();
?>
✗ 실수 2: MYSQLI_NUM으로 설정했는데 컬럼명으로 접근

컬럼 순서가 바뀌거나 쿼리가 수정되면 인덱스가 맞지 않아 데이터를 잘못 읽는다.

<?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)
}
?>
✓ 권장사항: PDO 또는 mysqli_fetch_assoc() 사용

직접 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'];
}
?>

 

5단계. 정리와 다음 단계

mysqli_fetch_array()의 fetch_mode는 반환 배열의 키 방식을 결정한다. MYSQLI_ASSOC를 명시적으로 지정하면 메모리를 절약하고 코드 의도를 명확하게 하며 버그를 줄인다. 기본값 MYSQLI_BOTH는 대량 데이터 조회에서 성능 문제를 일으키므로 절대 쓰지 말자. 하지만 이왕이면 PDO나 mysqli_fetch_assoc()처럼 더 직관적인 함수를 쓰는 게 좋다. 이 글의 권장사항을 참고해 지금 당신의 프로젝트에서 쓰고 있는 mysqli_fetch_array() 호출을 모두 명시적인 fetch_mode로 바꾸거나, PDO로 마이그레이션하면 메모리 효율성과 코드 품질이 함께 개선될 것이다.