updated_at: 2025-10-22 14:23

mysql2 패키지를 이용한 Node.js와 MySQL(MariaDB) 연동 가이드

1. mysql2 패키지란?

mysql2mysql 패키지와 API 호환성을 유지하면서 성능을 개선하고, 최신 JavaScript 기능인 Promiseasync/await를 기본적으로 지원하는 차세대 MySQL 드라이버입니다.

mysql의 콜백 지옥(Callback Hell) 문제를 해결하고 더 빠르고 안정적인 코드를 작성할 수 있어, 현재 Node.js 생태계에서 가장 널리 사용되는 MySQL 드라이버입니다.

2. 설치

npm install mysql2

3. mysql 대비 mysql2의 장점

기능 mysql mysql2 설명
비동기 처리 콜백(Callback) 방식만 지원 Promise & async/await 기본 지원 "콜백 지옥"을 해결하고, 코드를 동기식처럼 작성하여 가독성/유지보수성 극대화
성능 상대적으로 느림 더 빠름 Prepared Statements를 더 효율적으로 처리하고, 더 빠른 프로토콜 파서를 사용
Prepared Statements 지원 향상된 지원 SQL 인젝션 공격을 더 효과적으로 방지하고, 반복 실행 시 성능 이점 제공
API 호환성 - 높음 기존 mysql 코드를 최소한의 수정으로 mysql2로 마이그레이션 가능

결론: 새로운 Node.js 프로젝트를 시작한다면, 특별한 이유가 없는 한 mysql2 패키지를 사용하는 것이 압도적으로 유리합니다.

4. mysql2의 두 가지 API 스타일

mysql2는 기존 mysql과의 호환성을 위해 두 가지 스타일의 API를 제공합니다.

  1. 콜백 API (require('mysql2')): mysql 패키지와 거의 동일한 방식으로 동작합니다. 마이그레이션 시 유용합니다.
  2. Promise API (require('mysql2/promise')): 모든 함수가 Promise를 반환하여 async/await와 완벽하게 호환됩니다. 이 방식을 사용하는 것을 강력히 권장합니다.

이 문서는 **Promise API (mysql2/promise)**를 기준으로 설명합니다.

5. mysql2/promise 사용법

1) 커넥션 풀 생성 (createPool)

mysql과 마찬가지로, 웹 애플리케이션에서는 createPool을 사용하는 것이 표준입니다.

주요 옵션

mysql 패키지의 createPool 옵션과 거의 동일하지만, 몇 가지 유용한 옵션이 추가되었습니다.

const mysql = require("mysql2/promise");

const pool = mysql.createPool({
  host: process.env.DB_HOST || "localhost",
  port: process.env.DB_PORT || 3306,
  user: process.env.DB_USER,
  password: process.env.DB_PASSWORD,
  database: process.env.DB_DATABASE,

  // --- 성능 및 안정성 관련 옵션 (권장) ---
  connectionLimit: 10,
  waitForConnections: true,
  queueLimit: 0,

  // --- mysql2 추가/변경 옵션 ---
  enableKeepAlive: true, // 비활성 연결이 끊어지는 것을 방지 (기본값: false)
  keepAliveInitialDelay: 0, // Keep-alive 패킷 전송 시작 전 대기 시간(ms) (기본값: 0)
});

2) 기본 쿼리 실행 예제 (async/await)

async/await를 사용하여 코드가 매우 간결하고 직관적으로 변합니다.

async function getUser(id) {
  try {
    const sql = "SELECT id, email, name FROM users WHERE id = ?";
    // pool.query()는 [rows, fields] 형태의 배열을 반환합니다.
    const [rows] = await pool.query(sql, [id]);
    return rows[0]; // 첫 번째 행만 반환
  } catch (error) {
    console.error("Query Error:", error);
    throw error; // 에러를 상위로 전파
  }
}

// 함수 사용
(async () => {
  const user = await getUser(1);
  if (user) {
    console.log("User Found:", user);
  } else {
    console.log("User not found.");
  }
})();

6. CRUD 예제 (async/await 기반)

1) Create (데이터 삽입 - INSERT)

async function createUser(userData) {
  try {
    const sql = "INSERT INTO users SET ?";
    // pool.execute()는 Prepared Statement를 사용하여 더 안전하고 빠릅니다.
    const [result] = await pool.execute(sql, [userData]);
    console.log("New user created! Inserted ID:", result.insertId);
    return result.insertId;
  } catch (error) {
    console.error("INSERT Error:", error);
    throw error;
  }
}

// 사용 예시
createUser({ name: "Async User", email: "async@test.com" });

query() vs execute()

  • query(): SQL 문을 직접 실행.
  • execute(): SQL 문을 미리 컴파일(prepare)한 후 실행. 반복적인 쿼리에서 성능이 더 좋고, SQL 인젝션 방어에 더 효과적입니다. 가급적 execute() 사용을 권장합니다.

2) Read (데이터 조회 - SELECT)

async function getAllUsers() {
  try {
    const [rows] = await pool.query(
      "SELECT id, name, email FROM users ORDER BY id DESC"
    );
    console.log(`Found ${rows.length} users.`);
    return rows;
  } catch (error) {
    console.error("SELECT Error:", error);
    throw error;
  }
}

3) Update (데이터 수정 - UPDATE)

async function updateUser(id, dataToUpdate) {
  try {
    const sql = "UPDATE users SET ? WHERE id = ?";
    const [result] = await pool.execute(sql, [dataToUpdate, id]);
    console.log("Changed Rows:", result.changedRows);
    return result.changedRows;
  } catch (error) {
    console.error("UPDATE Error:", error);
    throw error;
  }
}

4) Delete (데이터 삭제 - DELETE)

async function deleteUser(id) {
  try {
    const sql = "DELETE FROM users WHERE id = ?";
    const [result] = await pool.execute(sql, [id]);
    console.log("Deleted Rows:", result.affectedRows);
    return result.affectedRows;
  } catch (error) {
    console.error("DELETE Error:", error);
    throw error;
  }
}

7. 트랜잭션(Transaction) 처리 예제

async/await 덕분에 "콜백 지옥" 없이 트랜잭션 로직을 매우 깔끔하게 작성할 수 있습니다.

async function createUserWithLog(userData) {
  let connection;
  try {
    // 1. 풀에서 커넥션 가져오기
    connection = await pool.getConnection();

    // 2. 트랜잭션 시작
    await connection.beginTransaction();

    // 쿼리 1: 사용자 생성
    const [userResult] = await connection.execute("INSERT INTO users SET ?", [
      userData,
    ]);
    const userId = userResult.insertId;

    // 쿼리 2: 로그 기록
    const logData = { user_id: userId, action: "created" };
    await connection.execute("INSERT INTO logs SET ?", [logData]);

    // 3. 모든 쿼리 성공 시 커밋
    await connection.commit();

    console.log("Transaction Complete. User created with ID:", userId);
    return userId;
  } catch (error) {
    // 4. 에러 발생 시 롤백
    if (connection) await connection.rollback();
    console.error("Transaction Error:", error);
    throw new Error("Failed to create user with log.");
  } finally {
    // 5. 커넥션 반납 (필수!)
    if (connection) connection.release();
  }
}

8. 버전별 주요 변경 사항 (mysql2)

mysql2는 지속적으로 발전해왔으며, 버전별로 몇 가지 중요한 변경점이 있습니다.

v2.x

  • mysql 패키지와의 높은 API 호환성을 유지하며 Promise 지원을 도입한 초기 안정 버전입니다.
  • 대부분의 기본 기능이 이 버전에서 확립되었습니다.
  • createPool 옵션에서 db 키를 사용해도 호환성을 위해 동작하는 경우가 많았지만, 문서는 database를 권장했습니다.

v3.x (최신)

  • 성능 개선: 내부 파서와 로직이 최적화되어 v2에 비해 더 나은 성능을 보입니다.
  • BigInt 지원: MySQL의 BIGINT 타입을 JavaScript의 BigInt 타입으로 직접 매핑하는 기능이 추가되었습니다 (supportBigNumbers: true, bigNumberStrings: false).
  • 더 엄격한 타입 및 옵션: database 옵션 사용이 표준화되었고, 일부 오래된 옵션들이 제거되거나 변경되었습니다.
  • ESM (ECMAScript Modules) 지원: import 구문을 더 잘 지원하도록 구조가 개선되었습니다.
  • 보안 강화: 종속성 업데이트 및 내부 로직 개선으로 보안이 강화되었습니다.

권장 사항:

  • 새로운 프로젝트를 시작한다면 최신 v3.x 버전을 사용하는 것이 좋습니다.
  • 기존 v2.x 프로젝트를 운영 중이라면, 큰 호환성 문제 없이 v3.x로 업그레이드할 수 있습니다. npm install mysql2@latest로 업데이트한 후 테스트를 진행해보는 것을 권장합니다.
평점을 남겨주세요
평점 : 2.5
총 투표수 : 1

질문 및 답글