갑자기 웹사이트가 먹통이 되고 에러 로그를 확인해보니 "ERROR 1040 (HY000): Too many connections" 메시지가 반복된다면, 동시접속자가 MySQL 서버의 최대 연결 수를 초과했다는 뜻이다. 특히 트래픽이 몰릴 때나 애플리케이션 버그로 연결이 제대로 종료되지 않을 때 자주 발생한다. 다만 대부분의 개발자들은 이 에러의 정확한 원인을 파악하지 못한 채 무조건 max_connections 값을 올리거나, 최악의 경우 서버를 재부팅해버린다. 이번 글에서는 이 문제가 정확히 뭔지, 왜 생기는지, 어떻게 근본적으로 해결하는지를 실무 기반으로 완벽하게 정리해보겠다.
먼저 내 MySQL 서버가 지금 몇 개의 연결을 허용하도록 설정되어 있는지 확인해야 한다. MySQL 클라이언트에 접속해서 다음 명령어를 실행하자.
SHOW VARIABLES LIKE 'max_connections';
✓ 정상 결과: Variable_name이 'max_connections'이고, Value가 151(기본값) 또는 그 이상으로 표시된다.
동시에 현재 연결 수도 확인해보자.
SHOW STATUS LIKE 'Threads_connected';
이 값이 max_connections에 가까워지거나 같으면, 곧바로 'Too many connections' 에러가 터질 수 있는 위험한 상태다.
1) 연결을 제대로 종료하지 않는 PHP 코드
가장 흔한 원인이다. DB 연결을 열고 나서 mysqli_close()나 PDO 연결을 null로 설정하지 않으면, 스크립트가 종료될 때까지 연결이 메모리에 남아있다.
✗ 잘못된 코드:
<?php
$conn = mysqli_connect('localhost', 'user', 'password', 'database');
$result = mysqli_query($conn, "SELECT * FROM users");
// 연결을 닫지 않고 스크립트 종료
?>
✓ 올바른 코드:
<?php
$conn = mysqli_connect('localhost', 'user', 'password', 'database');
if (!$conn) {
die('Connection failed: ' . mysqli_connect_error());
}
$result = mysqli_query($conn, "SELECT * FROM users");
while ($row = mysqli_fetch_assoc($result)) {
echo $row['name'];
}
mysqli_close($conn); // 반드시 닫기
?>
2) 오래된 연결이 타임아웃되지 않음
PHP 스크립트가 오래 걸리는 작업을 수행할 때, DB 연결이 idle 상태로 유지되고 있으면 MySQL이 계속 메모리를 점유한다.
3) 커넥션 풀 설정 누락
고트래픽 사이트에서 개별 PHP 요청마다 새로운 DB 연결을 만들면, 동시에 수백 개의 연결이 쌓일 수 있다.
긴급으로 연결 수를 늘려야 한다면, 다음과 같이 임시로 설정할 수 있다.
✓ 현재 세션에서만 적용:
SET GLOBAL max_connections = 500;
다만 이 방법은 MySQL 재시작 후 사라진다. 영구적으로 적용하려면 my.cnf 파일을 수정해야 한다.
✓ 영구 설정 (CentOS/Linux):
sudo vi /etc/my.cnf
다음 라인을 [mysqld] 섹션에 추가하거나 수정하자.
[mysqld]
max_connections = 500
그 후 MySQL을 재시작한다.
sudo systemctl restart mysqld
⚠️ 주의: max_connections를 무작정 크게 설정하면 서버 메모리 부족으로 MySQL이 응답 불가 상태(crash)에 빠질 수 있다. 서버 사양에 맞춰 적절한 값(일반적으로 100~1000)을 설정하자.
이미 쌓여있는 좀비 연결(idle 상태)을 정리하려면, MySQL의 SHOW PROCESSLIST로 확인 후 kill할 수 있다.
SHOW PROCESSLIST;
오래 유휴 상태인 연결을 찾았다면:
KILL [connection_id];
예를 들어, ID가 45인 연결을 종료하려면:
KILL 45;
한 번에 모든 유휴 연결을 정리하는 스크립트:
SELECT GROUP_CONCAT('KILL ',ID,';') FROM INFORMATION_SCHEMA.PROCESSLIST WHERE TIME > 300 AND COMMAND != 'Sleep';
이를 실행하면 300초 이상 유지된 연결을 일괄 종료한다.
PHP 최신 표준인 PDO를 쓴다면, 다음과 같이 연결을 명시적으로 닫자.
<?php
try {
$pdo = new PDO('mysql:host=localhost;dbname=mydb', 'user', 'password');
$stmt = $pdo->prepare('SELECT * FROM users WHERE id = ?');
$stmt->execute([1]);
$result = $stmt->fetchAll();
} catch (PDOException $e) {
echo 'Connection failed: ' . $e->getMessage();
} finally {
$pdo = null; // 연결 해제
}
?>
또는 다음처럼 서버에 'wait_timeout' 값을 설정해서, 자동으로 유휴 연결을 끊도록 할 수 있다.
SET GLOBAL wait_timeout = 600; -- 10분 후 자동 종료
| 잘못된 접근 | 올바른 접근 |
|---|---|
| 무조건 max_connections 값을 1000 이상으로 올리기 | 서버 메모리를 고려해 적절한 값(보통 200~500) 설정 후, 연결 누수 원인 제거 |
| "에러가 나면 그냥 MySQL 서버 재부팅" | SHOW PROCESSLIST로 원인 파악 후, 근본 문제 해결 |
| 여러 PHP 스크립트에서 매번 새로운 DB 연결 생성 | 연결 객체를 재사용하거나, 싱글톤 패턴으로 하나의 연결 유지 |
| 연결 닫지 않고 스크립트 종료 기대 | 명시적으로 mysqli_close() 또는 $pdo = null 실행 |
에러가 발생했을 때 확인해야 할 순서:
1. SHOW STATUS LIKE 'Threads_connected'로 현재 연결 수 확인
2. SHOW PROCESSLIST로 idle 상태 연결 찾기
3. 애플리케이션 로그에서 DB 연결 에러 패턴 분석
4. PHP 코드에서 mysqli_close() 호출 누락 여부 검토
5. 필요시 wait_timeout/interactive_timeout 조정
6. max_connections 값을 점진적으로 상향(한 번에 500씩 증가시키지 말 것)
7. 여전히 에러 발생하면 서버 메모리 추가 or 데이터베이스 분산 검토
MySQL 'Too many connections' 에러는 단순한 설정값 증가만으로는 근본적으로 해결되지 않는다. 문제는 대부분 애플리케이션 단계에서 연결을 제대로 관리하지 못하는 데 있다. 작은 연결 누수가 모여서 시스템 장애로 번지는 것처럼, 평소 코드 리뷰와 DB 연결 습관이 중요하다. 이 글의 SHOW PROCESSLIST 진단 방법과 연결 종료 패턴을 참고해서 지금 당신의 프로젝트를 한 번 점검해보면, 예상치 못한 트래픽 급증에도 안정적인 서버를 유지할 수 있을 것이다.