MySQL 8.4 쿼리 파라미터 바인딩 오류 수정
목차
MySQL을 8.4로 업그레이드한 후 데이터베이스 쿼리 중 일부가 간헐적으로 실패하는 현상을 발견했다. 원인을 추적해보니 LIMIT 절에 파라미터 바인딩을 사용했을 때 mysql2 라이브러리가 정상적으로 처리하지 못하는 이슈였다.
문제의 발단
보통 SQL 쿼리를 동적으로 작성할 때는 SQL 인젝션을 방지하기 위해 값을 파라미터로 바인딩한다. 예를 들어 페이지네이션이 필요한 경우라면 이런 식으로 작성하는 게 표준 패턴이다:
SELECT * FROM blindbox WHERE status = ? LIMIT ? OFFSET ?
이 방식은 ? 자리에 값을 안전하게 주입해주는 방식인데, MySQL 버전이 올라가면서 LIMIT 절에서는 이 파라미터 바인딩이 제대로 동작하지 않기 시작했다. 데이터베이스 드라이버 입장에서는 LIMIT 값이 정수여야 한다는 강한 제약이 있고, 일부 버전에서는 이를 엄격하게 검증하기 시작한 것 같다.
왜 LIMIT는 특별한가
일반적인 WHERE 절의 조건값들은 문자열, 숫자, 날짜 등 다양한 타입을 처리해야 하므로 파라미터 바인딩 로직이 유연하게 설계되어 있다. 하지만 LIMIT와 OFFSET은 다르다. 이 절들은 반드시 음이 아닌 정수여야 하고, 쿼리 최적화 단계에서 미리 알려져야 데이터베이스 엔진이 제대로 실행 계획을 세울 수 있다. 따라서 일부 데이터베이스나 드라이버 버전에서는 LIMIT 값을 파라미터로 받기를 거부하거나, 받더라도 타입 강제가 엄격해지는 경향이 있다.
해결: 쿼리에 값을 직접 삽입
결국 LIMIT과 OFFSET 값은 애플리케이션 코드에서 유효성을 검증한 후 쿼리 문자열에 직접 삽입하기로 결정했다. blindbox-db.ts에서 쿼리를 생성하는 부분을 수정했다:
// Before
const query = `SELECT * FROM blindbox WHERE status = ? LIMIT ? OFFSET ?`;
const result = await db.query(query, [status, limit, offset]);
// After
const query = `SELECT * FROM blindbox WHERE status = ? LIMIT ${parseInt(limit)} OFFSET ${parseInt(offset)}`;
const result = await db.query(query, [status]);
LIMIT 값들은 먼저 정수로 파싱하고, 쿼리 문자열에 직접 포함시킨다. 이렇게 하면 mysql2 드라이버도 쿼리 해석 시점에 명확하게 정수 리터럴로 인식할 수 있다.
트레이드오프 고민
이 방식의 문제점은 SQL 인젝션 공격 가능성이 이론상 열린다는 것이다. 그런데 LIMIT 값은 본질적으로 페이지 번호나 조회 수 같은 정수만 유효하므로, parseInt(limit) 같은 강타입 변환만 거치면 실제로는 안전하다. 문자열이나 특수문자가 들어오면 NaN이 되어 쿼리가 실패하고, 이는 오류 로깅으로 발견할 수 있다. 또한 우리 팀에서는 클라이언트 쿼리 파라미터를 스키마 검증하고 있어서 이 부분의 추가 방어층도 있었다.
결과적으로 이건 "완벽한 보안"과 "실제 동작"의 트레이드오프였고, 후자가 더 중요했다. 멈춘 서비스보다는 강화된 인증과 로깅으로 보호하는 게 현실적이었다.
배운 점
이번 작업을 통해 몇 가지를 깨달았다. 첫째, 라이브러리나 데이터베이스 버전 업그레이드는 일부 엣지 케이스를 수면 위로 올린다는 것. LIMIT 파라미터 바인딩이 항상 안정적인 줄 알았지만, 실제로는 벤더나 버전마다 다를 수 있다. 둘째, 코드리뷰나 테스트 전략을 짤 때 "파라미터 바인딩 = 항상 안전"이라는 관행만 믿고 넘어가면 안 된다는 것. 특정 절(LIMIT, GROUP BY 크기 등)에서는 예외가 있을 수 있다. 셋째, 팀과 함께 이런 fix를 논의할 때는 "보안성"과 "실용성"을 동시에 따져야 한다는 점이다.
이제 blindbox-db 레이어의 모든 LIMIT/OFFSET 호출이 이 패턴으로 정리되었고, MySQL 8.4 이상에서도 안정적으로 동작한다.
🛒 이 글과 어울리는 추천 상품
*위 링크는 쿠팡파트너스 활동의 일환이며, 일정액의 수수료를 제공받을 수 있습니다.
댓글 0
첫 댓글 달아줘.