/인프라/ERROR 1040 Too many connections 해결: MySQL 커넥션 고갈 진단·복구 런북
인프라MySQLERROR 1040

ERROR 1040 Too many connections 해결: MySQL 커넥션 고갈 진단·복구 런북

MySQL ERROR 1040 Too many connections를 30초 만에 진단하고 즉시 복구하는 런북. SHOW PROCESSLIST·kill부터 max_connections·wait_timeout, HikariCP·Django·Laravel 풀 설정까지 복붙 가이드.

ERROR 1040 Too many connections 해결: MySQL 커넥션 고갈 진단·복구 런북

지금 이 에러를 보고 들어왔다면 — 3분 안에 살립니다

애플리케이션 로그나 콘솔에 다음 원문이 찍혀 있을 겁니다.

TEXT
ERROR 1040 (HY000): Too many connections

이 에러는 문법 오류도, 쿼리 오류도 아닙니다. MySQL/MariaDB 서버가 열어둔 커넥션 슬롯(max_connections)을 전부 소진해서 새 접속을 거부하는 상태입니다. PostgreSQL의 FATAL: sorry, too many clients already가 백엔드 프로세스 상한이라면, MySQL은 하나의 서버 프로세스 안에서 스레드 단위로 커넥션을 관리한다는 점이 다릅니다. 즉 슬롯이 꽉 찼을 뿐 서버 자체는 살아 있고, Sleep 상태로 놀고 있는 커넥션만 정리하면 즉시 복구됩니다.

적용 범위: MySQL 5.7 / 8.0, MariaDB 10.x, AWS RDS/Aurora MySQL 호환. 이 글은 "명령 먼저, 설명은 뒤"로 구성했습니다. 지금 서비스가 죽어 있다면 아래 순서대로 그대로 복붙하세요.

  • 1단계 진단(30초) → 슬롯 고갈 확진
  • 2단계 복구 → root 예약 슬롯 접속 + kill + 임시 상향
  • 3단계 차단 → my.cnf 튜닝 + 커넥션 풀 설정

30초 확진: 슬롯이 꽉 찼는지 3개 명령으로 판정

먼저 접속만 된다면(모니터링용 커넥션, 관리자 세션 등) 아래를 실행합니다. 앱 계정으로 접속이 거부돼도, root는 예약 슬롯이 있어 대부분 들어갑니다(다음 섹션 참고).

SQL
-- 현재 열린 커넥션 수
SHOW STATUS LIKE 'Threads_connected';
-- 서버 기동 후 최대로 도달했던 커넥션 수
SHOW STATUS LIKE 'Max_used_connections';
-- 허용 상한
SHOW VARIABLES LIKE 'max_connections';
-- 슬롯을 점유 중인 세션 전체 (누가 범인인지)
SHOW FULL PROCESSLIST;
-- 인증/네트워크 실패로 끊긴 접속 누적치
SHOW STATUS LIKE 'Aborted_connects';

출력값을 이렇게 읽습니다.

항목정상 판단고갈 판단의미
Threads_connectedmax_connections보다 여유 있음max_connections에 근접/동일지금 열려 있는 커넥션 수
Max_used_connectionsmax_connections보다 작음max_connections와 같음한 번이라도 상한에 닿았다는 증거
max_connections워크로드 대비 충분기본값 151에 묶여 있음허용 슬롯 상한
Aborted_connects낮게 유지빠르게 증가인증 실패·타임아웃 (슬롯 고갈과 별개 원인)

Max_used_connectionsmax_connections와 같으면 슬롯 고갈 확진입니다.

SHOW FULL PROCESSLIST 결과에서는 Command 컬럼과 Time 컬럼을 봅니다.

TEXT
+-----+--------+-----------------+------+---------+------+-------+------------------+
| Id  | User   | Host            | db   | Command | Time | State | Info             |
+-----+--------+-----------------+------+---------+------+-------+------------------+
| 812 | appuser| 10.0.1.20:51022 | shop | Sleep   | 240  |       | NULL             |
| 813 | appuser| 10.0.1.20:51044 | shop | Sleep   | 238  |       | NULL             |
| 820 | appuser| 10.0.1.21:33002 | shop | Query   | 0    | ...   | SELECT ...       |
+-----+--------+-----------------+------+---------+------+-------+------------------+

CommandSleep이고 Time이 큰 커넥션이 잔뜩 쌓여 있다면, 애플리케이션 커넥션 풀이 커넥션을 반납하지 않고 붙잡고 있는 누수 신호입니다. 특정 Host(파드 IP나 서버 IP)에 커넥션이 몰려 있으면 그 앱이 범인입니다.

원인 분기표: 진짜 슬롯 고갈인가, 다른 문제인가

관측 지표실제 원인대응 방향
Threads_connectedmax_connections, Sleep 다수커넥션 풀 누수/과다kill + 풀 사이즈·idle timeout 조정
Aborted_connects 급증, Threads_connected는 여유비밀번호 오류·방화벽·네트워크 끊김슬롯 문제 아님. 인증/네트워크 점검
파드 스케일아웃 직후 급증파드 수 × 풀 사이즈 > 상한풀 사이즈 축소 또는 RDS Proxy/ProxySQL 도입
RDS/Aurora에서 낮은 상한인스턴스 클래스 연동 기본값파라미터 그룹에서 max_connections 조정

Aborted_connects만 오르는데 Threads_connected는 여유롭다면 1040은 부차적 증상이고 실제로는 인증 폭주나 네트워크 문제일 수 있으니 방향을 틀어야 합니다.

즉시 복구: root 예약 슬롯으로 들어가 kill 하기

MySQL은 CONNECTION_ADMIN(8.0) 또는 SUPER(5.7) 권한 계정에 슬롯 1개를 예약해 둡니다. 그래서 앱 계정이 1040으로 거부돼도 root는 접속되는 경우가 많습니다. 먼저 들어갑니다.

Bash
mysql -u root -p

접속되면 Sleep 커넥션부터 정리합니다. 개별 kill은 이렇게 합니다.

SQL
KILL 812;
KILL 813;

수십 개를 일일이 치기 어렵다면, kill 문을 자동 생성해서 한 번에 실행합니다.

SQL
-- 10분(600초) 이상 Sleep인 커넥션에 대한 KILL 문 생성
SELECT CONCAT('KILL ', id, ';') AS kill_stmt
FROM information_schema.processlist
WHERE command = 'Sleep' AND time > 600;

출력된 KILL ...; 문들을 복사해 붙여 실행하면 됩니다. 셸에서 원라이너로 처리하려면 mysqladmin이 편합니다.

Bash
# 현재 프로세스 목록 확인
mysqladmin -u root -p processlist

# 특정 id 강제 종료
mysqladmin -u root -p kill 812

# Sleep 커넥션 id를 뽑아 한 번에 kill (셸 원라이너)
mysql -u root -p -N -e \
"SELECT id FROM information_schema.processlist WHERE command='Sleep' AND time>600" \
| while read id; do mysqladmin -u root -p'YOUR_PW' kill "$id"; done

⚠️ 비밀번호를 -p'PW'로 붙이면 셸 히스토리에 남습니다. 급한 복구 후에는 history -c로 지우거나 .my.cnf를 사용하세요.

슬롯을 비운 뒤에도 트래픽이 몰려 금방 다시 찬다면, 재기동 없이 상한을 임시로 올립니다.

SQL
SET GLOBAL max_connections = 500;
-- 반영 확인
SHOW VARIABLES LIKE 'max_connections';

이 명령은 런타임 임시 조치입니다. MySQL을 재시작하면 my.cnf 값으로 되돌아갑니다. 즉 급한 불을 끄는 용도이며, 반드시 아래 3단계로 영구 설정과 근본 원인(풀 누수)을 잡아야 합니다. 상한을 무작정 올리면 커넥션당 메모리(정렬/조인 버퍼 등)로 인해 OOM 위험이 커진다는 점도 기억하세요.

재발 차단 1: my.cnf 서버 튜닝

/etc/my.cnf 또는 /etc/mysql/my.cnf[mysqld] 섹션에 영구 값을 지정합니다.

INI
[mysqld]
# 동시 접속 상한 (워크로드·메모리 고려해 산정)
max_connections = 500
# 단일 계정이 독점하지 못하도록 계정별 상한
max_user_connections = 200
# 비대화형(앱) 커넥션이 유휴 상태로 유지되는 최대 초 (기본 28800초=8시간이 문제)
wait_timeout = 600
# mysql 클라이언트 등 대화형 세션 유휴 상한
interactive_timeout = 600

wait_timeout을 8시간에서 10분으로 줄이면, 풀이 반납을 놓친 유휴 커넥션을 서버가 알아서 회수합니다. 적용 후 재시작하고 검증합니다.

Bash
sudo systemctl restart mysqld
SQL
SHOW VARIABLES LIKE 'max_connections';
SHOW VARIABLES LIKE 'max_user_connections';
SHOW VARIABLES LIKE 'wait_timeout';

각 값이 설정대로 나오면 성공입니다. 값이 반영되지 않았다면 편집한 파일이 실제 로드되는 설정 파일이 아닐 수 있으니 mysqld --help --verbose | grep -A1 "Default options"로 로드 경로를 확인하세요. RDS/Aurora는 my.cnf 대신 파라미터 그룹에서 조정하며, Aurora 기본 max_connections는 인스턴스 클래스 메모리에 연동된 공식으로 계산되므로 인스턴스를 키우거나 파라미터를 명시적으로 덮어써야 합니다(공식 파라미터 문서 확인 필요).

재발 차단 2: 프레임워크별 커넥션 풀 설정

근본 원인은 대부분 애플리케이션입니다. 핵심 원칙: 커넥션의 최대 수명(maxLifetime)을 서버 wait_timeout보다 짧게 잡아, 서버가 끊기 전에 앱이 먼저 정리하도록 합니다.

HikariCP (Spring Boot)

PROPERTIES
# application.properties
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.idle-timeout=300000      # 5분
spring.datasource.hikari.max-lifetime=570000      # 9.5분 < wait_timeout(600s)
spring.datasource.hikari.connection-timeout=3000

max-lifetimewait_timeout보다 반드시 짧게 두는 것이 핵심입니다. 그렇지 않으면 서버가 끊은 죽은 커넥션을 풀이 재사용하려다 오류가 납니다.

Django

Python
# settings.py
DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.mysql",
        "CONN_MAX_AGE": 60,   # 커넥션 재사용 최대 초. wait_timeout보다 짧게
        "OPTIONS": {"connect_timeout": 5},
    }
}

Django는 요청 종료 시 커넥션을 정리하지만, CONN_MAX_AGE를 무한대(None)나 너무 크게 두면 워커 수 × 커넥션이 누적됩니다. wait_timeout보다 작게 잡으세요.

Laravel / PDO

PHP
// config/database.php
'mysql' => [
    // ...
    'options' => [
        PDO::ATTR_PERSISTENT => false, // ★ 영속 커넥션은 누수·잔존 위험, 기본 false 유지
        PDO::ATTR_TIMEOUT => 5,
    ],
],

PDO::ATTR_PERSISTENT => true는 커넥션을 프로세스에 붙여둬 슬롯 고갈을 유발하기 쉽습니다. 특별한 이유가 없으면 끄는 것이 안전합니다.

Node.js (mysql2)

JavaScript
const mysql = require('mysql2/promise');
const pool = mysql.createPool({
  host: 'db.internal',
  user: 'appuser',
  database: 'shop',
  connectionLimit: 15,     // 인스턴스당 최대 커넥션
  waitForConnections: true,
  queueLimit: 0,
  idleTimeout: 60000,      // 유휴 커넥션 회수(60초)
  enableKeepAlive: true,
});

컨테이너·서버리스 환경의 함정

쿠버네티스에서 파드가 10개로 스케일아웃되고 각 파드가 connectionLimit=15라면 순간 150 커넥션을 요구합니다. 상한 산정은 반드시 (파드/워커 수) × (풀 사이즈)로 계산하세요. Lambda 같은 서버리스는 동시성만큼 커넥션이 폭증하므로, 애플리케이션 풀 대신 RDS Proxy·ProxySQL 같은 커넥션 풀링 미들웨어를 앞단에 두어 실제 DB 커넥션 수를 통제하는 방식이 실무에서 점점 표준이 되고 있습니다.

마무리: 진단 → 복구 → 차단 체크리스트

한 화면에 요약합니다.

SQL
-- ① 진단
SHOW STATUS LIKE 'Threads_connected';
SHOW STATUS LIKE 'Max_used_connections';
SHOW VARIABLES LIKE 'max_connections';
SHOW FULL PROCESSLIST;

-- ② 즉시 복구 (root 접속 후)
SELECT CONCAT('KILL ', id, ';') FROM information_schema.processlist
  WHERE command='Sleep' AND time>600;   -- 출력 복사 실행
SET GLOBAL max_connections = 500;        -- 임시(재기동 시 초기화)

-- ③ 영구 차단: my.cnf에 max_connections/wait_timeout,
--    앱 풀에 maxLifetime < wait_timeout
  • Max_used_connections가 상한에 닿았는지로 고갈 확진
  • Sleep 커넥션 kill로 즉시 슬롯 확보
  • SET GLOBAL은 임시 조치임을 인지하고 my.cnf 영구화
  • 각 앱 풀 사이즈 합계가 상한을 넘지 않는지 재계산
  • Threads_connected, Aborted_connects 모니터링 알람 설정 (상한의 80% 도달 시 경보 권장)

마지막으로 재발 방지의 핵심은 모니터링입니다. Threads_connectedmax_connections의 80%를 넘으면 알람이 울리도록 설정해 두면, 다음 번에는 1040을 만나기 전에 손을 쓸 수 있습니다.

자주 묻는 질문 (FAQ)

Q. SET GLOBAL max_connections로 올렸는데 재시작하니 원래대로 돌아왔어요. A. 정상입니다. SET GLOBAL은 런타임 임시값이라 재기동하면 my.cnf 설정으로 복귀합니다. 영구 반영하려면 [mysqld]max_connections 값을 적고 재시작한 뒤 SHOW VARIABLES LIKE 'max_connections';로 확인하세요.

Q. root로도 접속이 안 돼 ERROR 1040이 떠요. A. 예약 슬롯까지 소진되었거나(다른 관리 세션 점유) CONNECTION_ADMIN/SUPER 권한이 없는 계정입니다. 로컬 소켓으로 다시 시도(mysql -u root -p --socket=/var/run/mysqld/mysqld.sock)하거나, 그래도 안 되면 mysqladmin으로 종료를 시도하고 최후에는 서비스 재시작으로 슬롯을 초기화하세요.

Q. Sleep 커넥션을 kill 했는데 금방 다시 꽉 차요. A. 애플리케이션 커넥션 풀이 계속 새 커넥션을 만들고 반납하지 않는 누수입니다. 파드/워커 수 × 풀 사이즈가 상한을 넘는지 재계산하고, HikariCP max-lifetimewait_timeout보다 짧게 설정한 뒤, 필요하면 RDS Proxy·ProxySQL로 커넥션을 집약하세요.

✦ ✦ ✦
편집 검토 · Editorial Review

이 글은 AI 에이전트가 자료 조사와 1차 초안 작성을 담당하고, 사람 편집자가 사실관계·출처·톤과 맥락을 검토한 뒤 발행했습니다. 환경(OS·버전)에 따라 결과가 다를 수 있으니 적용 전 공식 문서를 함께 확인하세요. 오류를 발견하시면 이메일로 제보해 주세요 — 확인 후 신속히 정정합니다.

초안 · AI (Content Reviewer)·검토 · Nodelog 편집자·발행 ·
관련 공식 문서MySQL 공식 매뉴얼

댓글

첫 번째 댓글을 남겨보세요.