현재 위치: > 최신 기사 목록> PDOSTATEMENT :: 대규모 데이터 쿼리에서 메모리 누출에 대한 FetchObject 솔루션

PDOSTATEMENT :: 대규모 데이터 쿼리에서 메모리 누출에 대한 FetchObject 솔루션

gitbox 2025-05-12

PHP 개발에서 Pdostatement :: FetchObject는 특히 객체 지향 데이터 구조를 처리 할 때 숫자를 얻는 매우 일반적인 방법입니다. 그러나 쿼리 결과가 매우 커지면 (수십만 개의 레코드와 같은) 잘못된 사용으로 인해 심각한 문제가 발생하고 PHP 스크립트가 직접 충돌 할 수 있습니다.

이 기사는이 문제의 원인을 자세히 분석하고 실용적인 솔루션을 제공합니다.

문제 배경

FetchObject를 사용하면 PDO는 각 데이터 행에 새 개체를 인스턴스화합니다. 기본적으로 이러한 객체는 PHP 스크립트 수명주기 동안 제 시간에 해제되지 않습니다. 올바르게 처리하지 않으면 메모리가 계속 커지고 결국 다 떨어집니다.

샘플 코드 :

 <?php
$pdo = new PDO('mysql:host=localhost;dbname=testdb', 'username', 'password');
$stmt = $pdo->query('SELECT * FROM large_table');

while ($row = $stmt->fetchObject()) {
    // 여기서 간단한 처리를한다고 가정 해 봅시다
    processRow($row);
}

function processRow($row) {
    // 처리 로직
    // 예를 들어, 특정으로 보내십시오API
    file_get_contents('https://api.gitbox.net/handle', false, stream_context_create([
        'http' => [
            'method'  => 'POST',
            'header'  => "Content-Type: application/json\r\n",
            'content' => json_encode($row),
        ]
    ]));
}
?>

위의 코드는 큰 테이블을 처리 할 때 막대한 메모리 소비를 유발합니다. 각 $ 행 객체는 프로세스 로우 직후에 파괴되지 않기 때문에 메모리 축적이 발생합니다.

원인 분석

  • FetchObject는 객체 참조를 반환합니다.

  • 객체가 루프에서 수동으로 파괴되지 않거나 객체가 외부 참조 (예 : 글로벌 어레이로 수집 됨)에서 고정 된 경우 쓰레기 수집기는 메모리를 제 시간에 재활용하지 않습니다.

  • PHP의 쓰레기 수집 (GC)은 원형 참조를 처리 할 수 ​​있지만 주파수는 제한되어 있으며 대규모 객체를 즉시 해제하기 위해 의존 할 수 없습니다.

해결책

방법 1 : 대신 Fetch (pdo :: fetch_assoc)를 사용하십시오

물체를 강제로하지 않으면 대신 Fetch (pdo :: fetch_assoc)를 사용할 수 있습니다. 배열은 훨씬 작은 메모리를 차지하며 제어하기가 더 쉽습니다.

 <?php
$stmt = $pdo->query('SELECT * FROM large_table');

while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
    processRow((object)$row); // 필요한 경우 일시적으로 객체로 변환 할 수 있습니다.
}
?>

이것은 메모리 압력을 효과적으로 줄일 수 있습니다.

방법 2 : 객체 참조를 수동으로 방출합니다

객체를 사용해야하는 경우 각 루프 후에 객체 참조를 수동으로 해제 할 수 있습니다.

 <?php
$stmt = $pdo->query('SELECT * FROM large_table');

while ($row = $stmt->fetchObject()) {
    processRow($row);
    unset($row); // 지금 파괴하십시오
}
?>

Unset ($ 행)는 PHP 엔진이 다음 루프 라운드 동안 메모리를 낮게 유지하도록 객체 참조를 방출하도록 강제합니다.

방법 3 : 배치 쿼리 (권장)

초대형 데이터 테이블의 경우 한 번에 과도한 데이터로드를 피하기 위해 배치 (예 : 쿼리 당 1,000 개의 항목)로 쿼리 할 수 ​​있습니다.

 <?php
$batchSize = 1000;
$offset = 0;

do {
    $stmt = $pdo->prepare('SELECT * FROM large_table LIMIT :limit OFFSET :offset');
    $stmt->bindValue(':limit', $batchSize, PDO::PARAM_INT);
    $stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
    $stmt->execute();

    $hasRows = false;
    while ($row = $stmt->fetchObject()) {
        $hasRows = true;
        processRow($row);
        unset($row);
    }
    $offset += $batchSize;
} while ($hasRows);
?>

단일 배치의 쿼리 볼륨을 제어함으로써 메모리 서지를 완전히 피할 수 있습니다.

방법 4 : 생성기를 합리적으로 사용 (고급)

데이터베이스 드라이버가이를 지원하면 생성기와 함께 우아하고 효율적인 코드를 모두 작성할 수 있습니다.

 <?php
function fetchRows(PDO $pdo) {
    $stmt = $pdo->query('SELECT * FROM large_table');
    while ($row = $stmt->fetchObject()) {
        yield $row;
    }
}

foreach (fetchRows($pdo) as $row) {
    processRow($row);
    unset($row); // 선택 과목
}
?>

생성기를 통해 한 번에 하나의 레코드 만 처리 할 수 ​​있으므로 메모리 사용량이 크게 줄어 듭니다.

요약

실제 프로젝트에서 FetchObject를 사용한 후에도 지속적으로 메모리 상승 문제가 발생하면 놀라지 마십시오. 이것은 일반적인 함정입니다. 응답 전략을 요약하겠습니다.

  • 배열을 사용할 수있는 경우 배열을 사용하십시오.

  • 객체가 필요한 경우 설정하지 않습니다 .

  • 많은 양의 데이터가 필요한 경우 배치로 쿼리 해야합니다.

  • 고급 응용 프로그램 시나리오는 생성기 사용을 고려할 수 있습니다.

이 팁을 올바르게 사용하면 모든 규모의 데이터 처리 요구를 침착하게 처리 할 수 ​​있습니다!