이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.
- 운영 DB에서 직접 SELECT, UPDATE, DELETE를 할 때 무엇을 조심해야 하는가?
- 데이터 정정 작업은 어떤 절차로 안전하게 수행해야 하는가?
- 실수했을 때 복구 가능성을 높이려면 무엇을 준비해야 하는가?
개요
운영 DB 직접 쿼리는 강력하고 위험하다. 작은 오타가 대량 데이터 변경이나 서비스 장애로 이어질 수 있다. 백엔드 개발자는 운영 쿼리를 일회성 작업이 아니라 배포와 같은 수준의 변경으로 다뤄야 한다.
원리
운영 DB 직접 쿼리의 원리는 “내가 지금 배포 없이 운영 데이터를 바꾸는 중”이라고 보는 것이다. SQL 콘솔에서 실행하더라도 영향은 코드 배포와 같거나 더 크다. 권한, 대상 건수, 실행 계획, lock, timeout, 백업, 기록, 검증을 갖추지 않으면 작은 수정이 데이터 사고가 된다.
백엔드 개발자는 운영 쿼리를 실행하기 전에 스스로 DBA에게 설명할 수 있어야 한다. 어떤 row를 왜 바꾸는지, 몇 건인지, 얼마나 오래 걸릴지, 실패하면 어떻게 되돌릴지, 어떤 지표가 나빠지면 멈출지를 정해야 한다.
SELECT도 조심
SELECT는 데이터를 바꾸지 않지만 부하를 만들 수 있다.
SELECT *
FROM orders
WHERE created_at >= timestamp '2020-01-01';대형 테이블 전체 조회는 DB I/O와 cache를 압박할 수 있다. 운영에서는 필요한 컬럼, 기간 제한, limit를 둔다.
SELECT id, status, created_at
FROM orders
WHERE created_at >= timestamp '2026-06-01'
ORDER BY id
LIMIT 100;가능하면 읽기 전용 계정이나 replica에서 조회한다. primary에서 ad-hoc 조회가 필요하다면 기간 조건, limit, statement timeout을 두고, 큰 정렬이나 전체 스캔이 예상되는 쿼리는 먼저 실행 계획을 확인한다.
UPDATE 절차
데이터 정정 전에는 같은 조건으로 대상 건수를 확인한다.
SELECT count(*)
FROM orders
WHERE status = 'CREATED'
AND created_at < timestamp '2026-01-01';샘플도 본다.
SELECT id, status, created_at
FROM orders
WHERE status = 'CREATED'
AND created_at < timestamp '2026-01-01'
ORDER BY id
LIMIT 20;그 다음 작은 batch로 변경한다.
UPDATE orders
SET status = 'EXPIRED'
WHERE id IN (
SELECT id
FROM orders
WHERE status = 'CREATED'
AND created_at < timestamp '2026-01-01'
ORDER BY id
LIMIT 1000
);DB별 문법과 lock 동작은 확인해야 한다.
PostgreSQL에서는 작업 세션에서 timeout을 작게 걸어 실수의 피해를 줄일 수 있다.
BEGIN;
SET LOCAL statement_timeout = '5s';
SET LOCAL lock_timeout = '1s';
-- 검증과 변경 SQL
COMMIT;MySQL도 MAX_EXECUTION_TIME optimizer hint나 세션 변수, lock wait timeout 등 버전별 장치를 검토할 수 있다. 핵심은 “무한정 기다리는 운영 쿼리”를 만들지 않는 것이다.
트랜잭션과 롤백
작은 정정은 트랜잭션으로 감싸고 확인 후 commit할 수 있다.
BEGIN;
UPDATE members
SET status = 'ACTIVE'
WHERE id = 10;
SELECT id, status
FROM members
WHERE id = 10;
COMMIT;
-- 또는 ROLLBACK;대량 작업은 하나의 트랜잭션으로 묶으면 rollback이 오래 걸릴 수 있다. batch와 백업, forward fix를 더 중요하게 본다.
대량 작업에서는 rollback이 항상 좋은 복구 전략이 아니다. 이미 많은 row를 바꾼 뒤 rollback하면 undo/redo, replication, lock 유지 시간이 커질 수 있다. 그래서 작은 batch, 진행률 테이블, 재시작 가능 조건, forward fix가 더 실용적인 경우가 많다.
작업 기록
운영 정정 작업에는 기록이 필요하다.
목적: 2026-06-30 쿠폰 상태 정정
대상: coupon_issues status CREATED 중 만료일 지난 12,430건
검증: SELECT count와 샘플 20건 확인
백업: PITR 가능 시점 확인
실행자: joseph
실행 시간: 2026-06-30 22:00 KST
검증 결과: EXPIRED 12,430건 증가실전 팁
- 운영 쿼리는 동료 리뷰를 받는다.
- SELECT에도 limit와 기간 조건을 둔다.
- UPDATE/DELETE 전 count와 샘플을 확인한다.
- 대량 작업은 batch로 나누고 진행률을 남긴다.
- 백업과 복구 가능성을 확인한다.
- 가능하면 read-only 계정, replica, 낮은 권한 세션에서 먼저 확인한다.
- 작업 전 중단 기준을 정한다. 예를 들어 lock wait 증가, replica lag 60초 초과, error rate 증가 같은 기준이다.
- 작업 후에는 변경 row 수, 검증 쿼리 결과, 모니터링 지표를 작업 기록에 남긴다.
위험 신호!
- 운영에서 복사한 SQL을 수정 없이 바로 실행한다.
- WHERE 없는 UPDATE/DELETE를 실행한다.
- 대상 건수 확인 없이 변경한다.
- 작업 기록이 없다.
- 실수 후 복구 방법을 모른다.
- SQL 클라이언트가 자동 commit인지 모른 채 운영 변경을 실행한다.
- timeout 없이 lock을 기다리다가 사용자 API까지 막는다.
- count와 sample은 봤지만 실행 계획과 인덱스 사용 여부를 확인하지 않는다.
확인 질문
- 운영 SELECT도 조심해야 하는 이유는 무엇인가?
- 데이터를 바꾸지는 않아도 대량 읽기로 DB I/O, cache, CPU를 압박할 수 있기 때문이다.
- UPDATE 전 count와 샘플을 확인하는 이유는 무엇인가?
- 조건이 의도한 대상과 맞는지, 예상보다 많은 데이터가 바뀌지 않는지 확인하기 위해서다.
- 운영 정정 작업 기록에 포함해야 할 것은 무엇인가?
- 목적, 대상 조건, 대상 건수, 백업 상태, 실행자, 실행 시간, 검증 결과, 롤백 또는 forward fix 계획이다.
- 운영 쿼리에 timeout을 거는 이유는 무엇인가?
- 잘못된 쿼리나 lock 대기가 무한히 이어져 사용자 트래픽과 다른 운영 작업을 막는 상황을 줄이기 위해서다.