MySQL 버전을 5.7 이상으로 업그레이드하거나 서버를 이전한 직후, 기존에 잘 동작하던 SQL 쿼리에서 'ERROR 1055 (42000): Expression #1 of SELECT list is not in GROUP BY clause' 에러를 만난 적이 있을 것이다. 소스 코드를 건드리지도 않았는데 갑자기 데이터베이스 조회 기능이 터지는 바람에 당황하기 쉽다. 다만 대부분의 개발자들은 이 에러의 정확한 발생 원인이나 SQL 표준 동작 방식을 모른 채, 그저 my.cnf 파일에서 모드를 꺼버리는 임시방편으로 넘어가는 경우가 많다. 이번에는 MySQL ERROR 1055가 정확히 왜 발생하는지, ONLY_FULL_GROUP_BY 옵션의 작동 원리와 서비스 중단 없이 안전하게 해결하는 3가지 방법을 완벽하게 정리해서 소개하겠다.
ERROR 1055는 GROUP BY 절에 포함되지 않은 컬럼을 SELECT 절에서 직접 조회할 때 발생한다. MySQL 5.7 버전부터 sql_mode의 기본값에 ONLY_FULL_GROUP_BY 옵션이 추가되었기 때문이다.
과거 MySQL 5.6 이하에서는 GROUP BY를 사용할 때 집계 함수(SUM, COUNT, MAX 등)로 감싸지 않은 컬럼이 SELECT 절에 존재하더라도 임의의 행(Row) 데이터를 가져와서 그냥 보여주었다. 하지만 이는 데이터의 불확실성을 초래한다. 그룹화된 여러 개의 행 중에서 어떤 행의 값이 출력될지 보장할 수 없기 때문이다. 따라서 SQL92 표준에 맞춰 무작위 추출을 막고 쿼리의 엄격함을 강제하도록 기본 설정이 바뀐 것이다.
예를 들어 회원별 최근 주문 내역을 가져오기 위해 GROUP BY user_id를 실행했다고 가정해 보자. 이때 GROUP BY에 명시되지 않은 order_date나 product_name을 SELECT 절에 그대로 적으면, MySQL은 어느 주문 건의 날짜와 상품명을 보여줘야 할지 알 수 없으므로 1055 에러를 던지며 쿼리 실행을 거부한다.
ERROR 1055를 해결하는 방법은 크게 서버 설정 변경, 세션 모드 변경, 그리고 SQL 쿼리 리팩토링의 3가지가 있다. 서비스의 환경과 권한에 따라 최적의 방법을 선택해야 한다.
| 해결 방법 | 적용 범위 | 장점 | 단점 |
|---|---|---|---|
| 1. my.cnf 글로벌 설정 수정 | 전체 서버 (재시작 필요) | 한 번 설정으로 모든 웹 애플리케이션 에러 해결 | DB 서버 재시작 필요, 데이터 엄격성 저하 |
| 2. SQL 쿼리 리팩토링 (권장) | 해당 쿼리 단건 | DB 표준 준수, 데이터 결과의 명확성 보장 | 기존 소스 코드 수정 공수 발생 |
| 3. ANY_VALUE() 함수 사용 | 해당 쿼리 단건 | 쿼리 구조 최소 변경으로 빠른 에러 회피 | MySQL 전용 함수로 타 DB 이식성 낮음 |
실무에서 자주 작성하는 잘못된 GROUP BY 쿼리와 이를 올바르게 수정하는 두 가지 접근 방식을 살펴보자.
다음은 카테고리별 상품 수와 대표 상품명을 조회하려다 ERROR 1055가 발생하는 전형적인 코드다.
✗ 잘못된 코드 (ERROR 1055 발생)
SELECT category_id, product_name, COUNT(*) as total_count
FROM products
GROUP BY category_id;이 쿼리는 category_id로 그룹화를 했지만, GROUP BY에 없는 product_name을 집계 함수 없이 SELECT 절에 포함시켰기 때문에 1055 에러가 터진다.
✓ 올바른 코드 1: SQL 표준 집계 함수 및 GROUP BY 컬럼 추가
-- 방법 A: GROUP BY절에 컬럼을 모두 명시
SELECT category_id, product_name, COUNT(*) as total_count
FROM products
GROUP BY category_id, product_name;
-- 방법 B: 대표값을 집계 함수(MAX, MIN 등)로 명확히 지정
SELECT category_id, MAX(product_name) as sample_product, COUNT(*) as total_count
FROM products
GROUP BY category_id;✓ 올바른 코드 2: MySQL ANY_VALUE() 함수 활용
-- 그룹 내 임의의 값을 가져오도록 명시적으로 지시
SELECT category_id, ANY_VALUE(product_name) as product_name, COUNT(*) as total_count
FROM products
GROUP BY category_id;출력 결과:
+-------------+------------------+-------------+
| category_id | product_name | total_count |
+-------------+------------------+-------------+
| 1 | 무선 키보드 | 15 |
| 2 | 4K 모니터 | 8 |
+-------------+------------------+-------------+PHP PDO를 사용하여 데이터베이스에 연결할 때 세션 차원에서만 이 모드를 해제하여 기존 코드의 동작을 유지하는 방법도 존재한다.
✓ 올바른 코드 3: PHP PDO 커넥션 세션 설정
<?php
try {
$pdo = new PDO('mysql:host=localhost;dbname=testdb;charset=utf8mb4', 'dbuser', 'password');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// 연결 직후 현재 세션의 sql_mode에서 ONLY_FULL_GROUP_BY 제거
$pdo->exec("SET SESSION sql_mode=(SELECT REPLACE(@@sql_mode, 'ONLY_FULL_GROUP_BY', ''))");
$stmt = $pdo->query("SELECT category_id, product_name, COUNT(*) FROM products GROUP BY category_id");
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
echo "Error: " . $e->getMessage();
}
?>
에러를 빠르게 해결하려고 구글링을 한 뒤 my.cnf 파일에 sql_mode="" 형태로 빈 값을 넣어버리는 경우가 많다. 이는 굉장히 위험한 방식이다.
✗ 잘못된 방식: sql_mode 전체 초기화
-- 글로벌 모드를 빈값으로 세팅하여 모든 안전장치 해제
SET GLOBAL sql_mode = '';이렇게 설정하면 ONLY_FULL_GROUP_BY뿐만 아니라 잘못된 날짜 입력을 막는 NO_ZERO_IN_DATE, 엄격한 테이블 데이터 변환을 검증하는 STRICT_TRANS_TABLES 같은 중요한 디폴트 모드까지 전부 꺼지게 된다. 결과적으로 유효하지 않은 데이터가 DB에 무단으로 입력되는 무서운 부작용을 초래한다.
✓ 올바른 방식: 기존 sql_mode에서 ONLY_FULL_GROUP_BY만 제거
-- 현재 설정된 sql_mode 확인
SELECT @@sql_mode;
-- 확인된 모드 문자열에서 ONLY_FULL_GROUP_BY 단어만 쏙 빼고 다시 설정
SET GLOBAL sql_mode = 'STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION';서버 설정 파일(my.cnf 또는 my.ini)에 영구 적용할 때도 `[mysqld]` 섹션 아래에 필요한 모드들만 조합하여 명시해야 시스템 보안과 데이터 일관성을 지킬 수 있다.
MySQL ONLY_FULL_GROUP_BY 설정은 데이터베이스의 엄격성과 데이터 신뢰성을 보장하는 핵심 안전장치다. 무작정 모드를 끄는 임시방편 대신 쿼리를 표준에 맞게 개선하는 작은 습관이 모여서 서비스의 안정성을 높이는 큰 효과를 만든다는 점을 잊지 말자. 이 글의 실전 쿼리 수정 가이드를 참고해 기존 소스 코드를 검토하고 올바른 집계 함수를 적용하면, 데이터 왜곡 없이 안전하고 완벽한 쿼리 결과를 얻을 수 있을 것이다.