UPDATE/DELETE 기초: 포인트 원장을 망치지 않는 ‘정정(ADJUST)’과 ‘소프트 삭제’ 전략

포인트 시스템을 실제로 운영하면 “완벽하게 들어오는 데이터”만 존재하지 않습니다. 이벤트 지급이 잘못되거나, 고객 문의로 일부 포인트를 되돌려야 하거나, 테스트 데이터가 섞여 들어오거나, 배치 로직이 한 번 삐끗할 수도 있습니다.

이때 초보자가 가장 쉽게 선택하는 방법이 기존 행을 UPDATE로 고치거나 DELETE로 지워버리는 것입니다. 하지만 포인트 원장은 “기록” 자체가 신뢰의 기반이기 때문에, 과거 기록을 조용히 바꾸거나 삭제하면 나중에 추적이 불가능해집니다. 오늘은 깔끔해 보이지만, 몇 달 뒤에는 “왜 잔액이 이렇게 되었지?”를 아무도 설명할 수 없는 상태가 될 수 있습니다.

이 단원의 목적: UPDATE/DELETE를 ‘원장 철학’에 맞게 쓰는 기준 세우기

UPDATE/DELETE는 강력하지만, 원장성 데이터를 다룰 때는 사용 기준이 필요합니다. 이번 글의 목표는 아래 3가지입니다.

  1. 포인트 시스템에서 UPDATE/DELETE를 써도 되는 경우와 피해야 하는 경우를 구분한다
  2. 기록을 고치지 않고도 결과를 바로잡는 정정(ADJUST) 패턴을 만든다
  3. 물리 삭제 대신 소프트 삭제(숨김 처리)를 적용해 추적 가능성을 유지한다

1) 원장 데이터는 “수정”보다 “추가”가 기본이다

포인트 원장은 회계의 장부와 비슷합니다. 장부의 기존 줄을 지우거나 고치는 대신, 잘못된 기록이 있었다면 반대되는 기록을 추가해 결과를 맞춥니다. 이렇게 하면 “무슨 일이 있었는지”가 남기 때문에 나중에 확인이 가능합니다.

이 관점에서 UPDATE/DELETE는 ‘금지’가 아니라 ‘제한적으로만 사용’이 됩니다. 예를 들어 메모를 수정하거나, 테스트 데이터를 숨기는 정도는 가능하지만, amount나 action_type을 바꿔서 결과를 바꾸는 방식은 위험합니다.

2) UPDATE 기본: 특정 행을 ‘조건으로’ 정확히 집어서 수정한다

UPDATE에서 가장 중요한 건 WHERE입니다. WHERE가 없으면 테이블 전체가 수정됩니다. 그래서 UPDATE는 항상 “먼저 SELECT로 대상이 맞는지 확인”하고, 그 다음 UPDATE를 수행하는 습관이 안전합니다.

메모 수정처럼 결과에 영향이 없는 컬럼부터 연습

-- 1) 수정 대상 확인 SELECT id, memo FROM point_history WHERE id = 1001;

-- 2) 결과에 영향이 없는 컬럼만 수정(예: memo)
UPDATE point_history
SET memo = '고객 문의로 지급 사유 메모 보강'
WHERE id = 1001;

memo처럼 “보조 정보”는 수정해도 원장 신뢰성이 크게 흔들리지 않습니다. 반면 amount/action_type 같은 핵심 값은 수정이 곧 과거의 사실을 바꾸는 일이므로 훨씬 조심해야 합니다.

3) DELETE 기본: 지우기 전에 ‘정말 지워도 되는 데이터인가’부터 정의

DELETE 역시 WHERE가 핵심입니다. 그리고 원장 테이블에서 DELETE는 더 조심해야 합니다. 포인트 내역을 DELETE로 지우면, 그 순간부터 SUM 결과가 달라지고 과거 추적이 끊어집니다.

그래서 원장 테이블에서는 보통 DELETE를 이렇게 제한합니다.

  • 테스트 데이터처럼 운영 의미가 없는 경우
  • 법적/정책적으로 삭제가 필요한 개인정보 영역(원장 자체가 아니라 별도 영역에서 처리하는 경우가 많음)
  • 실수로 들어온 데이터라도, 이미 외부에 노출되었거나 정산에 영향을 줬다면 “정정 기록”으로 처리

그래서 등장하는 방식이 ‘소프트 삭제’다

4) 소프트 삭제: DELETE 대신 숨김 플래그를 둔다

소프트 삭제는 행을 삭제하지 않고, “삭제된 것처럼 취급”하는 방식입니다. 가장 단순한 방법은 hide(또는 is_deleted) 같은 플래그 컬럼을 두는 것입니다.

-- 컬럼 추가(이미 있다면 생략) ALTER TABLE point_history ADD COLUMN hide TINYINT NOT NULL DEFAULT 0; -- 소프트 삭제 처리 UPDATE point_history SET hide = 1 WHERE id = 1002;

이렇게 하면 데이터는 남아 있으므로 추적이 가능하고, 조회 시에는 hide = 0 조건을 추가해 “보이지 않게” 만들 수 있습니다.

-- 정상 데이터만 조회 SELECT id, action_type, amount, ref_id, created_at FROM point_history WHERE uid = @uid AND hide = 0 ORDER BY created_at DESC, id DESC;

소프트 삭제는 운영에서 특히 유용합니다. “지워야 할 것 같지만 확신이 없다”는 상황에서 일단 숨겨두고 영향도를 확인할 수 있기 때문입니다.

5) 정정(ADJUST): 기존 기록을 바꾸지 않고 결과를 바로잡는 방식

포인트 정정은 보통 두 가지 상황에서 필요합니다.

  • 과지급: 포인트가 더 들어갔다 → 차감 정정 필요
  • 미지급: 포인트가 덜 들어갔다 → 적립 정정 필요

이때 핵심은 “과거 기록을 수정”하지 말고, 정정 내역을 새로 INSERT로 남겨 차이를 메운다는 점입니다. action_type을 ADJUST로 두고, amount는 + 또는 -로 사용합니다.

(1) 과지급 정정: 음수 ADJUST 추가

-- 예: 200포인트가 과지급되었으니 -200 정정 INSERT INTO point_history (uid, point_type, action_type, amount, ref_id, memo, created_at) VALUES (@uid, 'FREE', 'ADJUST', -200, 'ADJUST_20251214_001', '과지급 정정', NOW());

(2) 미지급 정정: 양수 ADJUST 추가

-- 예: 150포인트가 미지급되었으니 +150 정정 INSERT INTO point_history (uid, point_type, action_type, amount, ref_id, memo, created_at) VALUES (@uid, 'FREE', 'ADJUST', 150, 'ADJUST_20251214_002', '미지급 정정', NOW());

이렇게 하면 “원래 지급 기록”과 “정정 기록”이 모두 남습니다. 나중에 고객 문의가 들어오면, 왜 정정이 들어갔는지 memo/ref_id로 추적할 수 있습니다.

6) 어떤 것을 UPDATE로 바꾸고, 어떤 것은 ADJUST로 남길까

기준을 한 문장으로 정리하면 아래와 같습니다.

  • 결과(잔액)에 영향을 주는 컬럼(amount, action_type, point_type)은 원칙적으로 “수정”하지 않는다
  • 필요하면 “정정 내역(ADJUST)”을 추가한다
  • 보조 정보(memo, hide)는 상황에 따라 UPDATE 가능

예외가 아예 없는 것은 아닙니다. 예를 들어 “테스트 데이터가 운영에 노출되기 전에 발견되었고, 아직 어떤 정산에도 쓰이지 않았다”면 UPDATE/DELETE로 정리하는 선택을 할 수도 있습니다. 다만 그 예외는 시스템마다 정책으로 정해두는 편이 좋습니다. 사람 판단에만 맡기면 같은 상황에서 처리 방식이 흔들립니다.

7) 정정 후 검증: ‘내역’과 ‘합계’를 동시에 확인

UPDATE/정정 INSERT를 수행한 뒤에는 항상 (1) 최신 내역, (2) 잔액 합계를 함께 확인하는 습관이 안전합니다.

-- (1) 최신 내역 확인 SELECT id, action_type, amount, ref_id, memo, hide, created_at FROM point_history WHERE uid = @uid ORDER BY created_at DESC, id DESC LIMIT 30; -- (2) 잔액 확인 (숨김 제외) SELECT uid, SUM(amount) AS balance FROM point_history WHERE uid = @uid AND hide = 0 GROUP BY uid;

이 두 쿼리를 세트로 갖고 있으면, 운영에서 데이터가 꼬였을 때 “어디서부터 틀어졌는지”를 빠르게 좁힐 수 있습니다.

다음 글 예고: SUM/COUNT로 잔액·월별 적립·사용 리포트 만들기(집계의 기본)

다음 글에서는 집계(aggregation)를 다룹니다. 포인트 시스템은 결국 원장을 기반으로 “요약 결과(잔액/기간 합계/통계)”를 만들게 됩니다. SUM/COUNT를 정확히 익히면, 잔액뿐 아니라 월별 적립량, 사용량, 만료량 같은 운영 지표를 SQL로 바로 뽑아낼 수 있습니다.

댓글

이 블로그의 인기 게시물

JOIN 기초: users와 point_history를 합쳐 ‘회원별 요약(잔액/최근 활동)’ 만들기

점검 SQL: “원장 합계(SUM) vs balance” 불일치를 찾아내고 원인을 좁히는 방법

EXPLAIN 기초: 점검/리포트 쿼리가 느려질 때 “왜 느린지” 확인하는 방법