MySQL에서 클라이언트가 타임아웃해도 쿼리는 멈추지 않는다: PROCESSLIST로 찾아 KILL QUERY로 중단하기
요약
클라이언트 타임아웃과 별개로 MySQL 서버 내부에서 무거운 쿼리가 계속 실행될 수 있습니다. 이는 클라이언트가 요청을 포기해도 DB는 작업을 지속하기 때문입니다. 본문에서는 `SHOW FULL PROCESSLIST`를 이용해 백그라운드 쿼리를 확인하고, `KILL QUERY [ID]` 명령어로 안전하게 중단하는 절차를 안내합니다.
핵심 포인트
- 클라이언트 타임아웃은 요청 포기일 뿐, DB는 작업을 계속함.
- `SHOW FULL PROCESSLIST`로 실행 중인 쿼리(Query)와 대기 상태(Sleep) 확인.
- `KILL QUERY [ID]` 명령어로 특정 쿼리만 안전하게 중단 가능.
- 트랜잭션 없는 수정 작업은 중단 시 변경 사항이 되돌아가지 않음에 유의.
MySQL에 무거운 집계 쿼리를 날렸고, 호출하는 쪽(클라이언트)이 타임아웃했습니다. 에러와 함께 끝났기 때문에 처리도 멈췄다고 생각했지만, 서버 내부에서는 쿼리가 여전히 실행되고 있었습니다.
최근 제가 (쿠지라) AI 코딩 어시스턴트(Claude Code)에게 실시간 DB의 집계를 맡겼을 때 이런 일이 발생했습니다. 제 쪽 명령어는 120초로 끊어졌지만, 서버 상에서는 같은 쿼리가 계속 실행 중이었고, 147초가 되어서야 Claude가 스스로 발견하고 멈출 때까지 아무도 막지 못했습니다. 읽기 전용(read-only) 쿼리였기 때문에 데이터는 안전했지만, 실시간 DB에 약 2분 반 동안 불필요한 부하를 준 셈이었습니다.
이 글에서는 그때 확인했던 절차를 '찾기 → 중단하기 → 원인 파악 → 다음부터 예방하기' 순서로 정리합니다.
클라이언트 측의 타임아웃은 '기다리는 것을 포기하는 것'에 불과합니다. 쿼리를 받은 MySQL 입장에서는 요청받은 일을 계속하고 있을 뿐이므로, 결과를 돌려주려고 상대가 없다는 것을 깨달을 때까지 계산을 계속할 수 있습니다.
PHP의 max_execution_time도 도움이 되지 않습니다. PHP 매뉴얼의 set_time_limit() 항목에 나와 있듯이, 이 상한은 스크립트 자체의 실행 시간만 계산하며, DB 문의 등 외부에서 소비된 시간은 포함하지 않습니다 (Windows에서만 실시간). Linux 서버에서는 PHP가 무거운 쿼리의 응답을 기다리는 동안 PHP 시계는 거의 움직이지 않습니다.
이번 흐름은 다음과 같았습니다.
- 제 쪽 명령어에서 서버 상의 PHP 스크립트를 실행하여 집계 쿼리 1개를 날림.
- 제 쪽 명령어가 120초 제한으로 끊어지고, PHP 프로세스도 종료됨.
- DB 프로세스 목록을 보니 같은 쿼리가 실행 중으로 남아있음 (경과 시간 147초).
- 그 자리에서 중단시킴.
SHOW FULL PROCESSLIST;
FULL을 붙이지 않으면 Info 열(쿼리 본문)이 앞 100자에서 잘려서, 긴 집계 쿼리는 구별하기 어렵습니다.
출력 예시 (설명을 위해 만든 예입니다):
Id User Host db Command Time State Info
12345 app_user 10.0.0.5:51234 appdb Query 147 Sending data SELECT SUM(EXISTS ...
| 열 | 확인할 포인트 |
|---|---|
Command | Query면 실행 중. Sleep은 연결이 대기만 하고 있음 |
Time | 그 상태가 된 이후의 초 수. 클라이언트 타임아웃보다 크다면 방치된 것을 의심함 |
Info | 쿼리 본문. 자신이 보낸 것인지 확인함 |
다른 사람의 연결까지 보려면 PROCESS 권한이 필요하지만, 자신의 쿼리만 찾을 것이라면 자신 사용자 스레드가 보이면 충분합니다.
-- 실행 중인 쿼리만 중단 (연결은 유지)
KILL QUERY 12345;
-- 연결 전체 끊기 (KILL CONNECTION과 동일)
...
방치된 것을 중단하는 것만 목적이라면, 영향이 적은 KILL QUERY부터 시도하면 충분합니다. 이번에도 이것으로 중단되었습니다.
주의사항:
- 바로 사라지지 않을 수 있음. KILL은 멈추기 위한 표시를 하는 것에 불과하며, 스레드가 결정된 타이밍에 확인될 때까지 시간이 걸릴 수 있습니다. 잠시 기다린 후 PROCESSLIST를 다시 확인해 보세요 -
- 수정 계열은 중간 변경 사항이 남음. 트랜잭션을 사용하지 않은
UPDATE/DELETE를 중단한 경우, 그때까지의 변경 사항은 되돌아가지 않습니다.SELECT는 걱정할 필요가 없습니다 - - Id 오타에 주의.
Info열에서 내용을 보고 나서 해당 행의 Id를 사용해야 합니다
쿼리의 골격은 다음과 같은 형태였습니다 (이름은 일반화했습니다).
users의 모든 행 u에 대해
SUM(EXISTS(table_a에 user_id = u.id인 행이 있는지))
SUM(EXISTS(table_b에 user_id = u.id인 행이 있는지))
...
바깥쪽 users는 수십만 행 규모이고, EXISTS (...)는 바깥쪽 1행마다 평가되는 상관 서브쿼리입니다. 그것이 5개나 됩니다. 게다가 안쪽 테이블 중 하나(수만 행 규모)에 user_id
인덱스가 없습니다. 옵티마이저가 재작성하는 경우도 있으므로 '매번 전체를 다시 읽었다'고는 단정할 수 없지만, 수십 초가 걸릴 것으로 예상되는 쿼리였다는 것은 확실합니다.
어떤 것들 모두 몇 초 만에 끝납니다.
-- 1. 대략적인 행 수 (InnoDB에서는 추정치. 자릿수를 알기에는 충분함)
SHOW TABLE STATUS LIKE 'users';
-- 2. 필터링 컬럼에 인덱스가 있는지
...
EXPLAIN으로 type이 ALL이고, rows가 큰 테이블이 있다면, 그 부분이 느림의 원인입니다. EXPLAIN ANALYZE는 실제로 실행해서 시간을 측정하므로, 예상 단계에서는 사용하지 않습니다.
정말로 알고 싶었던 것은 '관련 테이블에 기록이 있는 이용자는 몇 퍼센트인가'였습니다. 나중에 이용자 50명을 골라 WHERE user_id IN (...)으로 다시 실행해 보니, 인덱스가 없는 같은 테이블에서도 0.05초 만에 반환되었습니다. 대상을 먼저 좁히면 읽는 양이 비교할 수 없을 만큼 줄어듭니다.
전체에 대한 정확한 숫자가 필요하다면, 여유 시간대에 돌리거나 읽기 전용 레플리카(read replica)에서 돌리는 것 등을 먼저 검토합니다.
MySQL 5.7.8 이상에서는 SELECT에 서버 측 실행 시간 제한(밀리초)을 설정할 수 있습니다.
-- 이 연결의 SELECT에 30초 상한선
SET SESSION max_execution_time = 30000;
하나의 쿼리에만 적용하고 싶을 때는, SELECT 바로 뒤에 옵티마이저 힌트 MAX_EXECUTION_TIME(밀리초)를 작성하는 방법도 있습니다. 서버 자체가 중단시키기 때문에 누락되는 것이 없습니다.
- 대상은
SELECT만 (UPDATE/DELETE에는 적용 안 됨) - MariaDB는 설정 이름과 단위가 다르므로 레퍼런스로 확인하세요. GLOBAL에 설정하면 기존 배치 작업까지 막을 수 있으니, 조사할 때는 세션 단위로
| 상황 | 할 일 | 주의점 |
|---|---|---|
| 클라이언트가 타임아웃했을 때 | SHOW FULL PROCESSLIST로 남아있는지 확인 | PHP의 실행 시간 제한은 DB 대기를 계산하지 않음 (Windows 제외) |
| 누락된 것을 발견했을 때 | Info에서 확인 후 KILL QUERY <Id> | |
| 바로 사라지지 않을 수 있음/갱신 계열은 중간 변경 사항이 남을 수 있음 | ||
| 전체 집계를 작성했을 때 | 행 수・인덱스・EXPLAIN을 먼저 확인 | EXPLAIN ANALYZE는 실행됨 |
| 비율만 알고 싶을 때 | 수십~수백 개의 샘플로 대체 가능 | IN (...)으로 먼저 좁히기 |
| 조사할 것의 예방책 | SET SESSION max_execution_time | SELECT만・밀리초 지정 |
AI에게 DB 작업을 맡길 때도 사람이 직접 할 때도, '타임아웃하면, 손에 있는 데이터가 아니라 서버를 본다'는 절차를 포함해 두면 안심할 수 있습니다.
원문 기사 (AI 측 관점에서 작성됨): https://kujiragames.com/2026/10/mysql-query-keeps-running/
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기