본문 바로가기
DataBase

MySQL에서의 예외 처리: 저장 프로시저 vs 프로그램 단의 예외 처리

by 대박플머 2024. 12. 2.

데이터베이스 작업을 할 때 우리는 종종 예외 상황을 만나게 됩니다. 특히, 대규모 트랜잭션을 처리하거나 중요한 비즈니스 로직을 다룰 때 예외 처리는 안정성을 보장하는 중요한 요소 중 하나입니다. MySQL을 사용할 때 예외 처리를 다루는 방법은 크게 두 가지로 나뉩니다. 하나는 저장 프로시저 내에서 직접 예외를 처리하는 방법 이고, 다른 하나는 프로그램 단(TypeScript)에서 예외를 처리하는 방법 입니다.
이 글에서는 두 방법에 대해 각각 살펴보고, 각 방법이 가지는 장점과 단점에 대해 설명하겠습니다. 또한, 실제로 사용할 수 있는 예제 코드도 함께 제공하여 이해를 돕고자 합니다.


1. MySQL 저장 프로시저에서 예외 처리

저장 프로시저는 데이터베이스 내에서 복잡한 로직을 한 번에 처리할 수 있도록 해주는 도구입니다. 예를 들어, 특정 조건에 따라 데이터를 업데이트하거나 삽입하는 작업을 묶어서 처리할 수 있습니다. 저장 프로시저의 강력한 기능 중 하나는 예외를 직접 처리할 수 있다는 점 입니다.MySQL에서는 DECLARE ... HANDLER 구문을 통해 특정 예외 상황이 발생했을 때 처리할 로직을 정의할 수 있습니다. 이렇게 하면 예외 상황에 맞춰 데이터베이스 레벨에서 즉각적인 처리가 가능하게 됩니다.

저장 프로시저 내에서의 예외 처리 예제

다음은 송금하는 로직에서 잔액이 부족할 경우 발생할 수 있는 예외를 처리하는 예제입니다.

DELIMITER $$

CREATE PROCEDURE transfer_funds(IN sender_id INT, IN receiver_id INT, IN amount DECIMAL(10,2))
BEGIN
  DECLARE insufficient_funds CONDITION FOR SQLSTATE '45000';  -- 사용자 정의 예외
  DECLARE CONTINUE HANDLER FOR insufficient_funds
    BEGIN
      -- 예외 발생 시 로그 남기기
      INSERT INTO error_log (error_message) VALUES ('Insufficient funds');
    END;

  -- 가상의 조건: 송금자의 잔액이 부족할 경우 예외 발생
  IF (SELECT balance FROM accounts WHERE id = sender_id) < amount THEN
    SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Insufficient funds';
  ELSE
    -- 잔액 이체 로직
    UPDATE accounts SET balance = balance - amount WHERE id = sender_id;
    UPDATE accounts SET balance = balance + amount WHERE id = receiver_id;
  END IF;
END$$

DELIMITER ;

이 예제에서 DECLARE ... HANDLER는 잔액이 부족할 때 발생하는 SQLSTATE '45000' 상태를 처리하도록 설정되었습니다. 예외가 발생하면 단순히 오류를 로그에 남기고 트랜잭션을 취소하는 대신, 우리가 원하는 특정 로직을 수행할 수 있습니다.

저장 프로시저에서 예외 처리를 하는 이유

  1. 즉각적인 예외 처리 : 예외가 발생했을 때 데이터베이스 레벨에서 바로 처리할 수 있습니다. 이는 서버 또는 클라이언트 측에서 추가적으로 예외를 처리하지 않아도 된다는 점에서 효율적입니다.
  2. 일관성 유지 : 저장 프로시저 내에서 모든 예외 처리 로직을 통일하면, 여러 클라이언트가 동일한 로직을 일관되게 사용할 수 있습니다. 즉, 프로시저를 호출하는 방식만 달라질 뿐, 데이터베이스 레벨에서 처리하는 로직은 동일하게 유지됩니다.
  3. 성능 최적화 : 프로그램 단에서 처리하는 것보다 데이터베이스 내에서 직접 처리하는 것이 더 빠를 수 있습니다. 특히 트랜잭션 처리에서 즉시 예외를 검출하고 대응할 수 있다는 점에서 성능적인 이점이 있습니다.

그러나 저장 프로시저 내에서의 예외 처리는 복잡한 로직을 처리하기에는 제한적일 수 있습니다. 저장 프로시저는 고정된 로직을 따르기 때문에, 프로그램 단에서 더 복잡한 로직을 구현하는 것에 비해 유연성이 떨어질 수 있습니다. 그래서 경우에 따라서는 프로그램 단에서 예외 처리를 해야 할 때도 있습니다.


2. 프로그램 단에서의 예외 처리

두 번째 방법은 프로그램 단에서 직접 예외를 처리하는 방법 입니다. 보통 Node.js나 TypeScript 같은 환경에서 데이터베이스와 상호작용할 때 이 방법을 사용합니다. 특히 비즈니스 로직이 복잡하거나, 사용자에게 좀 더 세밀한 피드백을 주어야 하는 경우에는 프로그램 단에서 예외를 처리하는 것이 더 적합할 수 있습니다.
프로그램 단에서의 예외 처리는 데이터베이스에서 발생하는 예외 상황을 프로그램이 직접 감지하고, 그에 따라 로직을 처리하는 방식입니다. 즉, SQL 쿼리를 실행하고 그 결과를 바탕으로 프로그램이 예외를 처리하게 됩니다.

프로그램 단에서 예외 처리 예제 (TypeScript + MySQL)

다음은 송금 로직을 TypeScript에서 직접 처리하는 예제입니다. 여기서는 저장 프로시저 대신 인라인 쿼리 를 사용하여 예외를 처리합니다.

import mysql from 'mysql2/promise';

async function transferFunds(senderId: number, receiverId: number, amount: number) {
  const connection = await mysql.createConnection({
    host: 'localhost',
    user: 'root',
    database: 'test',
    password: 'password',
  });

  try {
    await connection.beginTransaction();

    // 송금자의 잔액 확인
    const [sender] = await connection.execute('SELECT balance FROM accounts WHERE id = ?', [senderId]);
    const senderBalance = sender[0].balance;

    if (senderBalance < amount) {
      throw new Error('Insufficient funds');
    }

    // 잔액 업데이트
    await connection.execute('UPDATE accounts SET balance = balance - ? WHERE id = ?', [amount, senderId]);
    await connection.execute('UPDATE accounts SET balance = balance + ? WHERE id = ?', [amount, receiverId]);

    await connection.commit();
    console.log('Funds transferred successfully');

  } catch (error) {
    await connection.rollback();

    // 오류 메시지에 따라 예외 처리
    if (error.message.includes('Insufficient funds')) {
      console.error('Transfer failed: Insufficient funds');
    } else {
      console.error('Transfer failed:', error.message);
    }
  } finally {
    await connection.end();
  }
}

// 예시 실행
transferFunds(1, 2, 100.00);

이 예제에서 transferFunds 함수는 송금자의 잔액을 확인한 뒤 조건에 맞춰 잔액을 업데이트하는 로직을 실행합니다. 만약 송금자의 잔액이 부족하면 예외를 발생시키고, 이를 catch 블록에서 처리합니다. 예외 발생 시에는 트랜잭션을 롤백하고, 에러 메시지를 출력하게 됩니다.

프로그램 단에서의 예외 처리 장점

  1. 유연성 : 프로그램 단에서 예외를 처리하면 복잡한 비즈니스 로직을 손쉽게 구현할 수 있습니다. 저장 프로시저는 고정된 로직에 의존하지만, 프로그램 단에서는 다양한 예외 상황에 맞춰 맞춤형 처리가 가능합니다.
  2. 상세한 피드백 제공 : 사용자가 예외 상황에 대해 더 정확하고 세부적인 피드백을 받을 수 있습니다. 프로그램은 예외 발생 시 그에 맞는 사용자 메시지를 보여줄 수 있으며, 이로 인해 사용자 경험이 향상됩니다.
  3. 트랜잭션 관리 : 프로그램 단에서 트랜잭션을 제어할 수 있어 복잡한 트랜잭션 처리 로직을 보다 쉽게 구현할 수 있습니다.

프로그램 단에서의 예외 처리 단점

  1. 성능 저하 : 데이터베이스 레벨에서 처리하는 것보다 프로그램에서 예외를 처리하면 성능이 다소 저하될 수 있습니다. 특히 트랜잭션이 많거나 복잡한 로직이 포함될 경우 성능이 문제가 될 수 있습니다.
  2. 추가적인 코드 작성 필요 : 프로그램에서 예외를 처리하려면 추가적인 로직을 구현해야 하므로 코드가 복잡해질 수 있습니다. 특히 복잡한 트랜잭션 로직을 처리할 때 코드가 길어질 수 있습니다.

결론: 어느 방법이 더 좋은가?

저장 프로시저 내에서 예외 처리를 하는 것과 프로그램 단에서 예외를 처리하는 것 모두 각각의 장점이 있습니다.

  • 저장 프로시저에서의 예외 처리 는 데이터베이스 레벨에서 처리하기 때문에 일관성성능 면에서 유리합니다. 특히 데이터베이스 트랜잭션의 일관성을 유지하고, 여러 클라이언트에서 동일한 로직을 사용할 때 적합합니다.
  • 반면, 프로그램 단에서의 예외 처리유연성사용자 피드백 측면에서 더 강력합니다. 복잡한 비즈니스 로직을 처리하거나 사용자에게 맞춤형 오류 메시지를 제공할 수 있는 점에서 유리합니다.
    따라서 복잡한 트랜잭션 처리와 사용자 경험이 중요한 경우 에는 프로그램 단에서의 예외 처리가 더 적합하고, 데이터베이스 레벨에서 성능과 일관성을 중시하는 경우 에는 저장 프로시저 내에서 예외 처리를 사용하는 것이 좋습니다.