IT·개발

PyMySQL InterfaceError: (0, ”) 원인과 해결 — Connection Leak 패턴

5월 19, 2026 2 min read

PyMySQL InterfaceError: (0, ”) 원인과 해결 — Connection Leak 패턴

이 글은 2021-03-04 Python 커뮤니티 도움 요청을 계기로 작성한 원본을 2026년 현재 시점에서 재정리한 것입니다. 코드와 절차는 원본 그대로이며, 원본에 없는 보충 분석은 [분석] 블록으로 별도 표기합니다.

2021년, Python 커뮤니티에서 도움 요청이 왔었습니다. API에서 받은 데이터를 DB로 보내주는 코드를 짰는데 아래 에러가 계속 발생한다는 것이었습니다.

pymysql.err.InterfaceError: (0, '')

저도 당시 많이 알지는 못했지만, Python ↔ MySQL 연결 흐름을 함께 짚어가다 보니 원인이 한 줄로 정리됐습니다 — DB Connection을 닫지 않아 발생하는 Connection Leak. 이 글에서는 에러 재현 → 최소 동작 코드 확인 → Connection Leak 원인 진단 → 해결 패턴(try…finally + with 문) → 2026년에 추가로 챙길 것 순서로 정리합니다.

그 과정에서 먼저 PyMySQL 연결 흐름 전체를 같이 짚어봤는데, 순서를 정리해두겠습니다.


1. 에러 재현 — 어떤 상황에서 이 에러가 발생하나

PyMySQL로 MySQL에 접속하는 교과서적인 흐름은 7단계입니다.

  1. pymysql 모듈 import
  2. pymysql.connect() — host, user, password, db 등 파라미터 지정
  3. Connection 객체에서 cursor() 호출 → Cursor 객체 획득
  4. Cursor 객체의 execute() — SQL 문장을 DB 서버에 전송
  5. fetchall() / fetchone() / fetchmany() — 결과 수신
  6. DML(INSERT / UPDATE / DELETE) 실행 후 Connection의 commit() — 확정 갱신
  7. Connection의 close() — 연결 종료

7단계를 순서대로 밟으면 정상 동작합니다. 문제는 코드 중간에 예외가 발생하거나, 마지막 close()를 빠뜨린 채 흐름이 끝나는 상황입니다. 아래 코드가 전형적인 위험 패턴입니다.

⚠️ 아래 자격 증명은 모두 더미값입니다. 본인 환경 값으로 바꾸세요.

import pymysql

conn = pymysql.connect(host='localhost', user='tester', password='7890',
                       db='testdb', charset='utf8')

curs = conn.cursor()

sql = """insert into customer(name, category, region)
         values (%s, %s, %s)"""
curs.execute(sql, ('홍길동', 1, '서울'))

conn.commit()

# ❌ conn.close() 누락 → Connection이 열린 채로 남는다

close()가 빠진 채로 반복 실행되면 연결이 쌓이고, 결국 새 연결을 열 수 없는 상태에 도달합니다.

PyMySQL InterfaceError: (0, '') 에러가 출력된 터미널 화면

2. 최소 동작 코드 — connect → execute → commit → close

원인 진단 전에, 비교 기준이 되는 정상 흐름 코드를 먼저 확인합니다.

⚠️ 아래 자격 증명은 모두 더미값입니다. 본인 환경 값으로 반드시 바꾸세요. (Python 3.6+ / PyMySQL 0.9.0+ / MySQL 5.7+ 기준. 환경 버전은 pip show pymysql로 확인하세요.)

import pymysql

conn = pymysql.connect(host='localhost', user='tester', password='7890',
                       db='testdb', charset='utf8')

curs = conn.cursor()  # 1. DB의 문을 열다

sql = """insert into customer(name,category,region)
         values (%s, %s, %s)"""

curs.execute(sql, ('홍길동', 1, '서울'))  # 2. 작업을 한다.
curs.execute(sql, ('이연수', 2, '서울'))  # 2. 작업을 한다.

conn.commit()  # 3. 저장한다.

conn.close()   # 4. DB의 문을 닫는다.

코드가 정상 흐름으로 끝날 때는 마지막 conn.close()가 실행되므로 문제없습니다. 문제는 중간 어딘가에서 예외가 발생해 close() 줄에 도달하지 못할 때입니다.


3. 왜 InterfaceError가 발생하나 — Connection Leak

핵심 진단은 한 줄입니다. DB의 문을 닫지 않아서 발생한 에러이며, 이 현상을 Connection Leak이라 부릅니다.

“SQL Connection을 열고 프로그램 중간에서 에러가 발생하면, Connection은 그대로 열려 있는 상태로 있을 수 있다. 이렇게 오픈되어 있는 Connection이 증가하면, 나중에 새로운 Connection을 오픈할 수 없게 되는데, 이를 Connection Leak이라 부른다. 이러한 Connection Leak을 막기 위하여 … try… finally 블록을 사용하여 finally에서 항상 Connection을 Close 해야한다.”

— 출처: pythonstudy.xyz — Python MySQL 쿼리

2026년 관점에서 조금 더 풀어보면 이렇습니다.

[분석] 한 프로세스에서 connect()만 반복하고 close()를 호출하지 않으면, MySQL 서버 측에서 허용하는 동시 연결 수(max_connections)의 한계에 결국 도달하게 됩니다. 이 한계를 넘으면 새 연결 요청이 거부되면서 InterfaceError로 이어집니다. max_connections 기본값은 MySQL 배포판·버전에 따라 다르므로 수치를 단정하기보다는, 연결을 열었으면 반드시 닫는 습관을 코드 구조로 보장하는 편이 중요합니다.


4. 해결 패턴 — try…finally + with 문

예외가 발생하더라도 conn.close()반드시 실행되도록 보장하려면 try...finally 블록을 사용합니다. Python 공식 문서에 따르면 finally 절은 예외 발생 여부와 무관하게 항상 실행됩니다(docs.python.org — try 문).

⚠️ 아래 자격 증명도 더미값입니다. 본인 환경 값으로 반드시 바꾸세요.

아래 예시는 INSERT와 SELECT 두 동작을 하나의 try...finally 블록 안에 묶어 conn.close()를 구조적으로 보장하는 패턴입니다.

import pymysql

conn = pymysql.connect(host='localhost', user='tester', password='7890',
                       db='testdb', charset='utf8')

try:
    # INSERT
    with conn.cursor() as curs:
        sql = "insert into customer(name,category,region) values (%s, %s, %s)"
        curs.execute(sql, ('이광수', 1, '서울'))

    conn.commit()

    # SELECT
    with conn.cursor() as curs:
        sql = "select * FROM customer"
        curs.execute(sql)
        rs = curs.fetchall()
        for row in rs:
            print(row)

finally:
    conn.close()

핵심 포인트 3가지입니다.

  • finally는 예외 발생 여부와 무관하게 실행됩니다try 블록 어디서 예외가 터져도 finallyconn.close()는 반드시 호출됩니다. Connection Leak을 구조적으로 차단하는 방식입니다.
  • with conn.cursor() as curs: — 블록을 벗어나는 순간 커서 리소스가 자동으로 해제됩니다. 커서를 따로 닫지 않아도 됩니다.
  • INSERT와 SELECT를 별도 커서로 분리한 이유 — 두 동작의 커서 라이프사이클을 명확히 분리해, 한 커서의 결과 세트가 다른 동작에 영향을 주지 않도록 하기 위해서입니다.

5. 2026년 회고 — 지금이라면 추가로 챙길 것

이 섹션은 2021년 원본에 없는 내용입니다. §1~§4가 원문 코드와 팩트를 다룬다면, 여기서는 2026년 현재 시점에서 돌아보며 드는 분석 의견을 덧붙입니다.

with 컨텍스트 매니저로 Connection까지 자동 close

with conn.cursor() as curs: 패턴은 원본에서도 이미 사용했습니다. 한 발 더 나아가, Connection 자체도 컨텍스트 매니저로 사용할 수 있습니다. PyMySQL 1.0.0 이상에서는 with pymysql.connect(...) as conn: 형태가 지원되어 finally: conn.close() 없이도 블록 종료 시 자동으로 닫힙니다. 다만 이 패턴을 도입하기 전에 반드시 pip show pymysql로 본인 환경의 버전을 먼저 확인해두는 것이 좋습니다.

연결 풀(Connection Pool)·ORM은 이 글 범위 밖

여러 요청을 동시에 처리하는 환경에서는 매번 connect()를 새로 호출하는 대신, 연결을 미리 만들어 재사용하는 Connection Pool 방식이 쓰입니다. SQLAlchemy·DBUtils 같은 라이브러리가 이 역할을 담당합니다. 그 내용은 이 글의 범위 밖이므로 키워드만 남겨둡니다.


마치며

DB의 문을 열고 작업 후에 저장하고 닫으세요. 2021년 답글에서 핵심은 이 한 줄이었고, 2026년 지금도 변하지 않았습니다. try...finally 패턴 몇 줄로 conn.close()를 구조적으로 보장해두면, 예외 흐름에서도 Connection이 방치되지 않습니다. PyMySQL을 처음 다루신다면, 연결을 여는 코드를 작성하는 순간부터 닫는 코드도 함께 써두는 습관을 권합니다.


참고 출처