데이터베이스 CRUD 기본 문법 정리

조회수 59

CRUD 문법 자체는 단순하지만 운영 데이터에서 어려운 부분은 문법이 아니라 의도한 행만 변경했는지 확인하는 과정이다. 잘못된 WHERE 하나가 여러 행을 수정하거나 삭제할 수 있고, 애플리케이션 입력을 문자열로 조합하면 SQL Injection으로 이어질 수 있다.

이 글은 MariaDB 10.11의 testdb 실습 데이터베이스에서 각 작업의 결과를 확인한 뒤 트랜잭션으로 되돌리는 흐름을 다룬다. 운영 DB에서 그대로 실습하지 않는다.

1. 재현 환경 만들기

USE testdb;

SHOW CREATE TABLE orders;
SELECT COUNT(*) AS order_count FROM orders;

예제의 핵심은 자동 증가 키, 허용 상태, 음수 금액 방지 조건을 먼저 정의하는 것이다. 애플리케이션 코드만 믿지 않고 DB도 잘못된 상태를 거부하도록 한다.

2. Create — 입력 후 생성 결과 확인

USE testdb;
START TRANSACTION;

INSERT INTO orders
    (order_no, customer_id, status, recipient_name, recipient_phone,
     postal_code, shipping_address, total_product_amount,
     shipping_fee, discount_amount, final_amount)
VALUES
    ('TEST-CRUD-001', 1, 'PENDING', '테스트 사용자', '010-0000-0000',
     '00000', '테스트 배송지', 25000.00, 0.00, 0.00, 25000.00);

SELECT LAST_INSERT_ID() AS new_order_id;

SELECT order_id, order_no, customer_id, status, final_amount, ordered_at
FROM orders
WHERE order_id = LAST_INSERT_ID();

ROLLBACK;

컬럼 목록을 생략하면 스키마 변경에 취약하다. 입력 직후 생성된 키와 저장된 값을 확인하면 기본값, 자료형 변환과 애플리케이션의 가정을 함께 검증할 수 있다.

3. Read — 필요한 열과 안정적인 순서 지정

USE testdb;

SELECT order_id, order_no, customer_id, status, final_amount, ordered_at
FROM orders
WHERE status = 'PENDING'
ORDER BY ordered_at DESC, order_id DESC
LIMIT 20;

SELECT *는 임시 점검에는 편하지만 애플리케이션 쿼리에서는 필요한 열을 명시하는 편이 안전하다. 같은 생성 시각이 존재할 수 있으므로 고유 키를 두 번째 정렬 기준으로 두어 결과 순서를 안정화한다.

4. Update — 변경 전에 대상을 잠그고 확인

START TRANSACTION;

SELECT order_id, status, final_amount
FROM orders
WHERE order_id = 7
FOR UPDATE;

UPDATE orders
SET status = 'PREPARING'
WHERE order_id = 7
  AND status = 'PAID';

SELECT ROW_COUNT() AS affected_rows;

SELECT order_id, status, updated_at
FROM orders
WHERE order_id = 7;

ROLLBACK;

ROW_COUNT()가 1이면 한 행이 변경됐고, 0이면 대상이 없거나 이미 다른 상태일 수 있다. 예상보다 크면 즉시 ROLLBACK하고 조건을 다시 점검한다. 실제 반영이 필요하고 검증 결과가 맞을 때만 마지막 문장을 COMMIT으로 바꾼다.

UPDATE 실행 후 ROW_COUNT와 변경 결과를 확인한 화면
트랜잭션 안에서 UPDATE 대상과 실제 변경 행 수를 함께 확인한 결과다.

5. Delete — 먼저 후보를 조회하고 제한 조건 유지

START TRANSACTION;

SELECT audit_log_id, action, created_at
FROM audit_logs
WHERE created_at < '2026-08-11'
ORDER BY audit_log_id;

DELETE FROM audit_logs
WHERE created_at < '2026-08-11';

SELECT ROW_COUNT() AS deleted_rows;
ROLLBACK;

조회와 삭제의 조건을 동일하게 유지한다. 대량 삭제는 잠금, 복제 지연과 실행 시간에 영향을 줄 수 있으므로 운영에서는 작은 배치로 나누고 백업·복구 가능 여부를 먼저 확인한다.

DELETE 실행 후 ROLLBACK으로 행이 복구된 결과
삭제 후보를 확인한 뒤 ROLLBACK하여 원래 행이 유지되는지 검증한 화면이다.

6. PHP에서는 값을 SQL 문자열에 이어 붙이지 않는다

$stmt = $pdo->prepare(
    'UPDATE orders
     SET status = :next_status
     WHERE order_id = :order_id
       AND status = :current_status'
);

$stmt->execute([
    'next_status' => 'PREPARING',
    'order_id' => 7,
    'current_status' => 'PAID',
]);

if ($stmt->rowCount() !== 1) {
    throw new RuntimeException('주문 상태가 변경되지 않았습니다.');
}

Prepared Statement는 값과 SQL 구조를 분리한다. 다만 테이블명, 컬럼명과 정렬 방향은 바인딩할 수 없으므로 사용자가 보낸 값을 그대로 넣지 말고 서버의 허용 목록에서 선택해야 한다.

7. 작업 전 확인표

  • 운영 데이터가 아닌 별도 환경에서 쿼리를 먼저 검증했는가
  • 변경 전 같은 WHERE로 대상 행을 조회했는가
  • 트랜잭션 안에서 영향 행 수와 변경 결과를 확인했는가
  • 복구 방법과 백업 상태를 확인했는가
  • 애플리케이션 입력은 Prepared Statement로 전달하는가

정리: CRUD의 핵심은 네 개의 SQL 문법을 외우는 것이 아니라 변경 대상, 영향 행 수와 최종 상태를 검증하는 것이다.

댓글 0

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