티스토리 뷰

반응형

웹 개발을 진행하다 보면 종종 PHP에서 Prepared Statement(준비 구문)를 활용해 SQL 쿼리를 작성했는데 결과가 한 건도 나오지 않는 상황에 부닥치게 된다. 특히 PHP 7.3 버전이나 그 이하 버전이라서 바인딩이 안 된다고 오해하기도 하지만, 사실 버전에 상관없이 Prepared Statement는 이미 오래전부터 지원되고 있다. 그럼에도 불구하고 쿼리를 직접 콘솔이나 phpMyAdmin 같은 DB 도구에 넣으면 값이 잘 나오는데, PDO(혹은 mysqli)에서 플레이스홀더를 사용하면 전혀 결과가 뜨지 않는 이유는 무엇일까? 직접 겪은 문제를 예로 들면서 자세히 살펴보자.


Prepared Statement와 바인딩의 원리

Prepared Statement는 SQL 인젝션을 방지하고 성능을 높이는 방법 중 하나다. 먼저 prepare() 단계에서 SQL 구문을 작성하고, 그 안에 :placeholder나 ? 같은 플레이스홀더를 삽입한다. 이후 execute() 메서드를 호출하면서 실제 값들을 바인딩한다. 예를 들어 다음과 같은 코드가 있다.

$sql = "SELECT * FROM steelmake 
        WHERE outdate BETWEEN DATE('2024-12-20') AND DATE('2025-01-20') 
          AND steel_item LIKE :bigsearch 
        ORDER BY outdate DESC, num DESC";

$stmt = $pdo->prepare($sql);
$params = [':bigsearch' => '%304 BB%'];
$stmt->execute($params);
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);

여기서 핵심은 execute($params)에 $params 배열을 반드시 전달해야 한다는 점이다. 만약 execute()를 그냥 호출하면 실제 쿼리에서는 :bigsearch가 바인딩되지 않아 검색이 되지 않는다.


바인딩 값이 보이지 않아 생기는 오해

SQL 디버깅을 위해 $sql 자체를 echo나 print_r로 출력해 보면, AND steel_item LIKE :bigsearch가 그대로 보일 수 있다. 이 때문에 “아직 :bigsearch 값이 들어가지 않았다”고 생각하기 쉬운데, 사실 이는 PDO 내부적으로 쿼리를 준비할 뿐, 개발자가 $sql을 찍어보는 시점에는 값이 치환되지 않은 문자열을 보는 것이다. 실제로 DB 서버에 전달될 때는 :bigsearch가 '%304 BB%'로 교체되어 정상 실행된다.

이 과정을 좀 더 자세히 확인하고 싶다면 아래와 같은 방법을 써볼 수 있다.

$stmt->debugDumpParams();

이 명령어는 내부적으로 어떤 쿼리가 실행되었는지와 어떤 파라미터가 바인딩되었는지 화면에 출력해 준다. 완전히 치환된 SQL 문장을 단일 문자열로 보여주지는 않지만, 적어도 바인딩된 값들이 제대로 들어갔는지 점검하는 데 도움이 된다. 더 구체적으로 확인하려면, 디버그용으로 $params를 이용해 직접 str_replace()로 치환한 문자열을 만들어 보는 방식도 있다.


날짜 범위와 다른 WHERE 조건 확인하기

종종 “검색어” 문제만 확인하다가 날짜 범위나 다른 조건을 놓칠 때도 있다. 예를 들어 다음과 같은 코드를 썼다고 가정해 보자.

WHERE outdate BETWEEN DATE('2024-12-20') AND DATE('2025-01-20')

데이터베이스 테이블에 저장된 outdate 컬럼이 실제로 이 범위에 포함되지 않는다면 당연히 결과가 나오지 않는다. 혹은 which = '1'(입고), which = '2'(출고) 같은 조건이 섞여 있어서 데이터가 더 줄어들 수도 있다. 따라서 숫자나 문자 범위가 제대로 매칭되는지, 대소문자나 공백 등이 일치하는지 세밀하게 확인해 보는 게 중요하다.


공백, 대소문자, 스펙 차이도 주의

검색어로 '%304 BB%'를 넣었는데, 실제 DB에는 '304BB'처럼 공백 없이 저장되어 있을 수도 있다. MySQL은 대체로 기본 콜레이션이 대소문자를 구분하지 않지만, 어떤 환경에서는 구분될 수 있으니 입력된 값이 정확히 어떤 형태인지까지 확인하면 더 확실하다. 아래처럼 범위를 크게 잡거나 LIKE 검색을 넉넉하게 설정해서 테스트해 보면 도움이 된다.

SELECT * 
FROM steelmake 
WHERE steel_item LIKE '%304%' 

이런 식으로 단계를 나눠 점차 구체화하면 문제 지점을 좁혀갈 수 있다.


Prepared Statement 실행 절차 점검

  1. SQL에 플레이스홀더(:placeholder) 사용
    예: AND steel_item LIKE :bigsearch
  2. prepare($sql) 후 바인딩
    예: $stmt->execute([':bigsearch' => '%304 BB%']);
  3. 데이터를 가져오기
    예: $result = $stmt->fetchAll(PDO::FETCH_ASSOC);

이 중 하나라도 빠지거나, execute() 부분에 $params를 전달하지 않으면 결과가 나오지 않는다. 특히 $stmt->execute();를 그냥 호출해 버리면 플레이스홀더가 그대로 남아 DB 쿼리에 반영되지 않는 상황이 벌어진다.


버전에 대한 오해

PHP 7.3에서 바인딩이 제대로 지원되지 않는다는 이야기를 들으면 혹할 수 있다. 그러나 Prepared Statement와 PDO 바인딩은 PHP 5.x 시절부터 이미 존재하고 널리 사용되어 왔다. 혹시 오래된 PHP 버전에서 PDO 확장 모듈이 설치되지 않았거나, PDO가 아닌 mysql_* 함수들을 사용해서 충돌이 있는 게 아닌지 확인해야 한다. 기본적으로 PHP 7.3 환경이라면 PDO 바인딩은 아무 문제 없이 작동한다.


플레이스홀더와 수동 쿼리의 차이

간혹 ‘수동으로 입력하면 결과가 나오는데, 플레이스홀더를 쓰면 안 나온다’며 헷갈릴 수 있다. 하지만 위에서 살펴본 것처럼 수동 쿼리는 예를 들어 이렇게 직접 작성한다.

SELECT * FROM steelmake
WHERE outdate BETWEEN DATE('2024-12-20') AND DATE('2025-01-20')
AND steel_item LIKE '%304 BB%'

Prepared Statement는 이 구문을 미리 준비해 두고, 실제 ‘%304 BB%’라는 값을 execute() 과정에서 전달한다. 만약 execute()에서 값을 넣지 않았다면 결국 steel_item LIKE :bigsearch 상태로 DB에 가므로 결과가 나오지 않는 건 당연한 일이다.


전체적인 흐름

정리하자면 다음 체크리스트가 있다.

  1. SQL 구문이 올바른지: 직접 DB 툴에 넣었을 때 기대한 결과가 나오는지 확인.
  2. 날짜 범위와 기타 조건: 실제 데이터 범위를 모두 커버하는지 검사.
  3. 공백, 대소문자 등: LIKE 검색 시 공백이나 철자 차이가 일치해야 한다.
  4. execute($params): 플레이스홀더에 바인딩할 값을 빠뜨리지 않았는지 확인.

이 과정을 한 번이라도 빠뜨리면 같은 오류가 쉽게 반복될 수 있다. 반면, 하나씩 짚어가며 확인하면 문제의 원인을 명확히 찾을 수 있다.


이상과 같은 방법을 통해 Prepared Statement에서 검색 결과가 나오지 않는 문제를 해결할 수 있다. 특히 $stmt->execute($params) 부분을 누락하면 아무리 문자열을 열심히 찍어봐도 검색어가 반영되지 않는다. 사소하지만 흔히 놓치기 쉬운 부분을 주의 깊게 살펴보는 습관만 들이면, PHP 개발에서 마주치는 많은 난관을 수월하게 넘어갈 수 있다. 데이터베이스와 직접 맞닿는 로직일수록 꼼꼼히 확인하고 점검하는 습관이 중요하다는 점을 기억해 두면 좋겠다.

반응형
댓글