SQL 9회차 : INSERT/UPDATE/DELETE 실무 패턴



SQL 9회차 : INSERT/UPDATE/DELETE 실무 패턴

INSERT/UPDATE/DELETE는 SQL에서 데이터를 “실제로 바꾸는” 핵심 명령어입니다. 초보자에게는 문법 자체보다도, 실무에서 사고 없이 안전하게 실행하는 방법(조건 설정, 영향 건수 확인, 트랜잭션 처리, 롤백 전략)이 더 중요합니다. 이 글에서는 초보자도 따라할 수 있도록, 실무에서 자주 쓰는 패턴을 예시와 함께 정리합니다.

요약

  • INSERT는 단건/다건/SELECT-INSERT/UPSERT로 나뉘며, 기본값/NULL/키 생성 규칙을 먼저 이해해야 합니다.
  • UPDATE는 WHERE 누락이 가장 큰 위험 요인입니다. 실행 전 영향 건수 확인 → 트랜잭션 → 검증 → 커밋 순서를 습관화합니다.
  • DELETE는 “소프트 삭제(상태값)”와 “하드 삭제(실제 제거)”를 구분하고, 운영에서는 소프트 삭제가 기본인 경우가 많습니다.
  • 실무 패턴의 핵심은 문법이 아니라 “안전장치(백업/로그/권한/검증)”를 같이 운영하는 것입니다.

목차

핵심 포인트

  • 영향 범위: 변경 대상이 “정말 그 행들만”인지 확인합니다. UPDATE/DELETE는 항상 같은 조건으로 SELECT를 먼저 실행합니다.
  • 영향 건수: “몇 건이 바뀌는지”를 숫자로 확인합니다. 예상보다 크면 즉시 중단합니다.
  • 트랜잭션: 운영 데이터 변경은 BEGIN → 검증 → COMMIT 또는 ROLLBACK 흐름으로 진행합니다.
  • 감사/복구: 소프트 삭제, 변경 이력 테이블, 백업/스냅샷 등 “되돌릴 수 있는 장치”를 준비합니다.
  • UPSERT: “있으면 UPDATE, 없으면 INSERT”는 실무에서 매우 흔합니다. DB별 문법 차이를 알고 선택합니다.
구분 실무 기본 패턴 자주 나는 실수 안전장치
INSERT 단건/다건/SELECT-INSERT/UPSERT 컬럼 순서 혼동, 기본값/NULL 오해 명시적 컬럼 지정, 제약조건 확인
UPDATE SELECT로 범위 확인 후 변경 WHERE 누락, 조인 업데이트 범위 과다 트랜잭션, 영향 건수 확인, 조건 재검증
DELETE 소프트 삭제 우선, 필요 시 하드 삭제 연관 데이터(참조 무결성) 미고려 FK 정책 확인, 단계적 삭제/아카이브

상세 설명: 실습용 테이블 예시와 전제

예시는 어떤 DB에서도 이해할 수 있도록 최대한 표준 SQL에 가깝게 작성합니다. 다만 DB마다 자동 증가 키, UPSERT 문법, 반환값(RETURNING) 등은 차이가 있을 수 있습니다.

예시로 “회원(users)”과 “주문(orders)” 테이블이 있다고 가정하겠습니다.

-- 예시 스키마(개념용)
-- users: 회원 기본 정보
-- orders: 주문 정보(사용자 참조)

-- users(id PK, email UNIQUE, name, status, created_at)
-- orders(id PK, user_id FK, amount, created_at)

초보자가 가장 많이 헷갈리는 지점은 아래 3가지입니다.

  • NULL vs 기본값: 값을 넣지 않으면 NULL이 되는지, DB 기본값이 들어가는지, 혹은 NOT NULL 제약으로 실패하는지 확인해야 합니다.
  • 키 생성 방식: 자동 증가(AUTO INCREMENT/IDENTITY/SEQUENCE)인지, 애플리케이션에서 생성하는지에 따라 INSERT 패턴이 달라집니다.
  • 제약조건: UNIQUE, FOREIGN KEY, CHECK가 걸려 있으면 INSERT/UPDATE/DELETE가 의도대로 되지 않을 수 있습니다.

INSERT 실무 패턴

1) 단건 INSERT: “컬럼을 명시”가 기본

실무에서는 컬럼을 생략하는 INSERT보다, 컬럼을 명시하는 INSERT가 안전합니다. 테이블 구조가 변경되어도 영향이 덜하고, 컬럼 순서 혼동을 줄일 수 있습니다.

INSERT INTO users (email, name, status, created_at)
VALUES ('dev01@example.com', '홍길동', 'ACTIVE', CURRENT_TIMESTAMP);
  • 포인트: 신규 컬럼이 추가되어도 기존 INSERT가 갑자기 깨지거나, 엉뚱한 컬럼에 값이 들어갈 위험을 낮춥니다.
  • 상태값(status): 운영에서는 ACTIVE/INACTIVE 같은 상태값을 두고, 삭제 대신 상태 변경으로 운영하는 경우가 많습니다.

2) 다건 INSERT: 여러 행을 한 번에 넣기

대량 등록이 필요하면 루프를 돌며 1건씩 INSERT하기보다, DB가 지원하는 범위에서 다건 INSERT가 효율적입니다.

INSERT INTO users (email, name, status, created_at)
VALUES
  ('dev02@example.com', '김하나', 'ACTIVE', CURRENT_TIMESTAMP),
  ('dev03@example.com', '이둘',   'ACTIVE', CURRENT_TIMESTAMP),
  ('dev04@example.com', '박셋',   'ACTIVE', CURRENT_TIMESTAMP);
  • 다건 INSERT는 “한 번에 실패/성공”이 될 수 있으므로, UNIQUE/FK 제약에 걸리는 데이터가 섞여 있으면 전체가 실패할 수 있습니다.
  • 대량 데이터는 배치 단위를 나누고(예: 500~5,000행), 실패 시 재처리 전략을 설계하는 것이 일반적입니다.

3) INSERT…SELECT: 조회 결과를 그대로 적재

실무에서 “백업 테이블 만들기”, “이관/아카이브”, “요약 테이블 적재” 같은 작업은 INSERT…SELECT 패턴이 핵심입니다.

-- 예: 비활성 회원을 아카이브 테이블로 적재(개념 예시)
INSERT INTO users_archive (id, email, name, status, archived_at)
SELECT id, email, name, status, CURRENT_TIMESTAMP
FROM users
WHERE status = 'INACTIVE';
  • 포인트: SELECT 범위를 먼저 검증해야 합니다. SELECT COUNT(*)로 예상 건수를 확인하는 습관이 좋습니다.
  • 중복 적재 방지: 아카이브 테이블에 UNIQUE 제약이 있거나, 이미 들어간 데이터는 제외하는 조건이 필요할 수 있습니다.

4) UPSERT(있으면 UPDATE, 없으면 INSERT)

초보자 단계에서는 “이메일이 있으면 이름/상태를 갱신하고, 없으면 신규 등록” 같은 요구가 자주 나옵니다. 이를 애플리케이션에서 SELECT 후 분기 처리할 수도 있지만, 동시성 상황에서는 레이스 컨디션(동시에 두 번 INSERT 시도)이 발생할 수 있습니다.

  • PostgreSQL: INSERT ... ON CONFLICT ... DO UPDATE
  • MySQL: INSERT ... ON DUPLICATE KEY UPDATE
  • SQL Server/Oracle: MERGE (주의점이 있어 신중히 사용)
-- (개념) UPSERT가 필요한 이유:
-- 1) 신규면 INSERT
-- 2) 이미 있으면 UPDATE
-- 3) 동시성에서도 UNIQUE 제약을 기준으로 안전하게 처리

사용 중인 DB에 맞는 UPSERT 문법을 선택하되, 핵심은 “UNIQUE 키(예: email)”를 기준으로 충돌을 처리한다는 점입니다.

UPDATE 실무 패턴

1) UPDATE는 “SELECT로 리허설”이 기본

UPDATE는 데이터 전체를 바꿀 수 있으므로, 실무에서는 아래 순서를 권장합니다.

  1. 같은 조건으로 SELECT를 먼저 실행해 대상 행을 눈으로 확인합니다.
  2. SELECT COUNT(*)로 영향을 받을 건수가 예상과 일치하는지 확인합니다.
  3. 트랜잭션을 시작하고 UPDATE를 실행합니다.
  4. 변경 결과를 다시 SELECT로 검증한 뒤 커밋합니다.
-- 1) 리허설 SELECT
SELECT id, email, status
FROM users
WHERE email LIKE '%@example.com'
  AND status = 'ACTIVE';

-- 2) 영향 건수 확인
SELECT COUNT(*) AS target_count
FROM users
WHERE email LIKE '%@example.com'
  AND status = 'ACTIVE';

-- 3) 실제 UPDATE
UPDATE users
SET status = 'INACTIVE'
WHERE email LIKE '%@example.com'
  AND status = 'ACTIVE';

가장 흔한 사고는 WHERE 조건 누락입니다. 그래서 실무에서는 “UPDATE 문만 덜렁 실행”하지 않고, 같은 조건으로 SELECT를 먼저 실행하는 습관이 중요합니다.

2) 부분 업데이트: CASE로 조건별 변경

여러 조건에 따라 값을 다르게 바꿔야 할 때는 CASE를 사용하면 한 번의 UPDATE로 처리할 수 있습니다.

-- 예: 특정 도메인 고객은 VIP로, 그 외는 NORMAL로
UPDATE users
SET status = CASE
  WHEN email LIKE '%@company.com' THEN 'VIP'
  ELSE 'NORMAL'
END
WHERE status IN ('ACTIVE', 'NORMAL', 'VIP');
  • 포인트: CASE를 쓰면 조건이 복잡해져도 “한 번의 트랜잭션”으로 묶을 수 있습니다.
  • 주의: WHERE가 넓어질 수 있으니 반드시 대상 범위를 먼저 SELECT로 확인합니다.

3) 조인 기반 UPDATE(개념): 연관 테이블을 기준으로 갱신

실무에서는 “주문이 1건 이상 있는 회원만 상태 변경”처럼, 다른 테이블을 기준으로 UPDATE하는 일이 많습니다. DB별 문법이 다르지만, 개념은 같습니다.

-- (개념) 주문이 있는 회원만 상태를 'CUSTOMER'로 변경한다고 가정
-- 실제 문법은 DB별로 다르므로, 먼저 아래와 같은 SELECT로 대상을 확정합니다.

SELECT u.id, u.email
FROM users u
WHERE EXISTS (
  SELECT 1
  FROM orders o
  WHERE o.user_id = u.id
);

조인/서브쿼리를 포함한 UPDATE는 “대상 범위가 예상보다 커지는” 문제가 자주 발생합니다. 따라서 EXISTS/IN 조건을 사용하더라도, 업데이트 대상이 정확한지 먼저 SELECT로 확정하는 것이 안전합니다.

DELETE 실무 패턴

1) 소프트 삭제(추천): 상태값으로 삭제 표시

운영 환경에서는 “삭제 요청”이 들어와도 데이터를 실제로 지우지 않고, 상태값을 바꾸는 소프트 삭제를 기본으로 쓰는 경우가 많습니다. 이유는 간단합니다. 나중에 복구해야 할 가능성이 있고, 삭제 이력/감사 요구가 생길 수 있기 때문입니다.

-- 소프트 삭제 예: status를 'DELETED'로 변경
UPDATE users
SET status = 'DELETED'
WHERE id = 1001
  AND status <> 'DELETED';
  • 소프트 삭제를 쓰면 “기본 조회에서 DELETED 제외” 규칙을 반드시 함께 운영해야 합니다.
  • 시간이 지나면 아카이브로 옮기거나, 정말 필요할 때만 하드 삭제를 수행합니다.

2) 하드 DELETE: 실제로 행을 제거

정말로 데이터를 지워야 하는 상황도 있습니다. 예를 들어 테스트 데이터 정리, 법적/정책적 요구에 따른 영구 삭제, 임시 적재 테이블 정리 등입니다. 이때도 UPDATE와 동일하게 “SELECT 리허설”이 핵심입니다.

-- 1) 먼저 대상 확인
SELECT id, email, status
FROM users
WHERE status = 'DELETED';

-- 2) 영향 건수 확인
SELECT COUNT(*) AS target_count
FROM users
WHERE status = 'DELETED';

-- 3) 실제 삭제
DELETE FROM users
WHERE status = 'DELETED';

삭제는 되돌리기 어렵기 때문에, 운영에서는 아래를 특히 주의합니다.

  • 참조 무결성(FK): users를 삭제하면 orders 같은 자식 테이블이 문제가 되는지 확인합니다.
  • 삭제 정책: FK가 ON DELETE CASCADE이면 연쇄 삭제가 발생할 수 있어, 영향 범위가 급격히 커질 수 있습니다.
  • 아카이브 우선: 삭제 전에 INSERT…SELECT로 백업(아카이브)하는 패턴이 자주 사용됩니다.

3) 단계적 삭제/정리(운영형): “나눠서 안전하게”

대량 삭제는 락(잠금)과 성능 문제를 유발할 수 있습니다. 실무에서는 한 번에 모두 지우기보다, 배치 단위로 나눠서 실행하거나, 특정 기간/범위로 제한해 점진적으로 정리합니다. DB별로 LIMIT/TOP/ROWNUM/CTE 등 문법이 다르므로, 사용 DB에 맞는 방식으로 “작게 나누는” 원칙을 적용합니다.

-- (개념) 대량 삭제는 배치 단위로 나누는 것을 권장
-- 예: created_at 기준으로 오래된 데이터부터 단계적으로 정리(문법은 DB별 조정 필요)

트랜잭션과 롤백: 실무 안전장치의 핵심

초보자에게 가장 큰 실무 격차는 “트랜잭션을 습관처럼 쓰느냐”입니다. 트랜잭션은 여러 변경을 하나의 묶음으로 처리해, 중간에 문제가 생기면 원상복구(ROLLBACK)할 수 있게 해줍니다.

  • 데이터 변경 전/후를 비교할 수 있도록, 같은 조건으로 SELECT를 실행해 결과를 확인합니다.
  • 운영에서는 권한(누가 변경 가능한지), 변경 로그(언제/무엇을), 백업(되돌릴 수 있는지)이 함께 움직입니다.
-- 트랜잭션 기본 흐름(개념)
BEGIN;

-- 변경 대상 사전 확인(필수)
SELECT COUNT(*) AS target_count
FROM users
WHERE status = 'ACTIVE';

-- 변경 실행
UPDATE users
SET status = 'INACTIVE'
WHERE status = 'ACTIVE';

-- 변경 결과 검증(필수)
SELECT COUNT(*) AS after_count
FROM users
WHERE status = 'INACTIVE';

-- 이상 없으면 커밋, 이상하면 롤백
COMMIT;
-- ROLLBACK;

DB 클라이언트(툴)에 따라 자동 커밋이 켜져 있을 수 있습니다. 운영 작업 전에는 “자동 커밋 설정”을 반드시 확인하는 것을 권장합니다.

실행 단계: 초보자를 위한 실무 체크리스트

  1. 목표 정의: 무엇을(어떤 테이블/어떤 조건) 얼마나(예상 건수) 바꾸는지 문장으로 정리합니다.
  2. 리허설 SELECT: UPDATE/DELETE와 동일한 WHERE로 SELECT를 실행해 대상 행을 확인합니다.
  3. 영향 건수 확인: SELECT COUNT(*)로 예상 건수와 일치하는지 확인합니다.
  4. 트랜잭션 시작: 운영 환경에서는 가능하면 트랜잭션으로 묶고, 변경 중간에 검증할 수 있게 합니다.
  5. 변경 실행: INSERT/UPDATE/DELETE 실행 후, 툴이 보여주는 영향 건수도 확인합니다.
  6. 사후 검증: 변경 전/후 비교 SELECT를 실행하고, 표본 데이터(몇 행)를 직접 확인합니다.
  7. 커밋/롤백: 결과가 기대와 다르면 롤백하고 원인을 분석합니다. 괜찮다면 커밋합니다.
  8. 기록: 어떤 쿼리를 어떤 이유로 실행했는지, 언제 누가 했는지 남기면 운영 품질이 올라갑니다.
단계 실행 내용 확인 기준
사전 SELECT 리허설, COUNT 확인 대상 행/건수가 예상과 일치
실행 BEGIN 후 DML 실행 영향 건수 확인, 오류 발생 여부
검증 변경 후 SELECT 재확인 전/후 비교 결과가 기대와 일치
마무리 COMMIT 또는 ROLLBACK 운영 로그/티켓/변경 이력 기록

추가로 생각해볼 점

  • 권한 분리: 운영 DB에서 누구나 UPDATE/DELETE를 할 수 있으면 사고 확률이 크게 올라갑니다. 읽기 전용 계정과 변경 계정을 분리하는 것이 좋습니다.
  • 감사 로그(Audit): “누가 언제 어떤 값을 무엇으로 바꿨는지”가 필요한 서비스가 많습니다. 변경 이력 테이블이나 DB 감사 기능을 검토해볼 수 있습니다.
  • 소프트 삭제 정책: 소프트 삭제를 쓰면 조회/통계에서 제외 규칙이 필수입니다. 공통 WHERE 조건, 뷰(View), ORM 필터로 일관성을 확보하는 방법을 고민해보세요.
  • 성능과 잠금: 대량 UPDATE/DELETE는 잠금과 인덱스/로그 부하를 유발합니다. 배치 단위 분할, 야간 작업, 인덱스 점검이 필요할 수 있습니다.
  • DB별 차이: UPSERT, RETURNING, LIMIT 기반 삭제 등은 DB마다 문법이 다릅니다. 팀 표준 DB에 맞춘 템플릿을 만들어두면 생산성이 올라갑니다.


이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

Reactions

댓글 쓰기

0 댓글