숏컷 코드
INSERT INTO posts (title, published) VALUES ('Hello SQL', false);
UPDATE posts SET published = true WHERE id = 10;
DELETE FROM posts WHERE id = 10;문법
WHERE 없는 UPDATE·DELETE는 전체 행을 대상으로 한다
UPDATE와 DELETE에 WHERE를 생략하면 테이블의 모든 행에 적용됩니다. 이는 의도치 않은 전체 데이터 손상의 가장 흔한 원인입니다. 데이터가 많은 테이블에서 WHERE 없이 실행하면 롤백하지 않는 한 되돌리기 어렵고, 롤백도 실행 시간만큼 걸립니다. DML을 실행하기 전에 같은 WHERE 조건으로 SELECT를 먼저 돌려 영향 범위를 확인하는 습관이 가장 기본적인 안전 장치입니다.
-- 먼저 SELECT로 범위 확인
SELECT * FROM posts WHERE id = 10;
-- 확인 후 UPDATE 실행
UPDATE posts SET published = true WHERE id = 10;INSERT, UPDATE, DELETE는 각각 넣기, 바꾸기, 지우기를 담당한다
INSERT는 새 행을 넣고, UPDATE는 기존 행 값을 바꾸고, DELETE는 행 자체를 지웁니다. 세 문장은 비슷해 보여도 "새 데이터 생성", "상태 변경", "행 제거"라는 역할이 다르므로, 먼저 무엇을 바꾸려는지 명확히 한 뒤 문장을 고르는 편이 안전합니다.
INSERT INTO posts (title, published) VALUES ('draft', false);
UPDATE posts SET published = true WHERE id = 10;
DELETE FROM posts WHERE id = 10;INSERT는 열 이름을 명시하면 스키마 변경에 안전하다
INSERT INTO posts VALUES (...) 형태로 열 이름을 생략하면 컬럼 순서에 의존합니다. 나중에 스키마가 바뀌거나 열 순서가 달라지면 예상치 못한 값이 들어갈 수 있습니다. 열 이름을 명시하면 스키마 변경에 독립적이고, 코드를 읽는 사람이 어떤 값이 어디 들어가는지 즉시 파악할 수 있습니다. RETURNING 절을 쓰면 INSERT 결과(특히 자동 생성된 ID)를 즉시 반환받을 수 있습니다.
-- 열 이름 명시 + RETURNING으로 생성된 ID 반환
INSERT INTO posts (title, user_id, published)
VALUES ('새 글 제목', 42, false)
RETURNING id, created_at;UPDATE는 MVCC로 인해 기존 행을 수정하지 않고 새 버전을 만든다
PostgreSQL의 UPDATE는 내부적으로 기존 행에 삭제 마킹을 하고 새로운 행 버전을 삽입합니다. 이것이 MVCC(다중 버전 동시성 제어)의 동작 방식입니다. 덕분에 UPDATE 중인 테이블을 읽는 다른 트랜잭션은 이전 버전을 계속 볼 수 있습니다. 그러나 갱신이 잦은 테이블에는 dead tuple이 쌓여 테이블이 부풀어 오르고, 이를 해소하기 위해 VACUUM이 필요합니다.
-- 대량 UPDATE 후에는 VACUUM으로 dead tuple 정리 권장
UPDATE posts SET view_count = view_count + 1 WHERE id = 10;
-- 이후 주기적 VACUUM (자동 AUTOVACUUM이 처리하지만 수동도 가능)
VACUUM posts;ON CONFLICT로 upsert 패턴을 구현한다
같은 키의 행이 있으면 업데이트하고 없으면 삽입하는 "upsert" 패턴은 PostgreSQL에서 INSERT ... ON CONFLICT로 구현합니다. 충돌 대상 열(UNIQUE 또는 PRIMARY KEY)을 명시하고, DO UPDATE SET으로 충돌 시 실행할 업데이트를 지정합니다. DO NOTHING을 쓰면 충돌 시 아무것도 하지 않고 조용히 무시합니다. 이 패턴은 멱등성(idempotent) INSERT가 필요한 배치 작업, 외부 데이터 동기화에서 특히 유용합니다.
INSERT INTO page_views (page_id, view_date, count)
VALUES (42, CURRENT_DATE, 1)
ON CONFLICT (page_id, view_date)
DO UPDATE SET count = page_views.count + EXCLUDED.count;RETURNING이 있으면 변경 직후 값을 다시 읽으려고 한 번 더 SELECT할 필요가 없다
PostgreSQL은 DML 뒤에 RETURNING을 붙여 실제로 삽입·수정·삭제된 행을 바로 돌려줄 수 있습니다. 자동 생성된 ID, 트리거가 채운 값, 갱신 후 최신 상태가 필요할 때 다시 SELECT를 따로 날리는 것보다 이쪽이 더 직관적입니다.
UPDATE posts
SET published = true
WHERE id = 10
RETURNING id, published, updated_at;체크포인트
| 상황 | 적합한 선택 |
|---|---|
| 새 행 삽입 시 자동 생성된 ID를 바로 받으려면 | INSERT ... RETURNING id |
| UPDATE·DELETE 전 영향 범위 확인 | 같은 WHERE 조건으로 먼저 SELECT |
| 있으면 업데이트, 없으면 삽입해야 할 때 | INSERT ... ON CONFLICT DO UPDATE SET |
| 충돌 시 아무것도 하지 않으려면 | INSERT ... ON CONFLICT DO NOTHING |
주의할 점
UPDATE와 DELETE에 WHERE 조건 없이 실행하면 테이블 전체 행이 영향을 받습니다. 실행 전에
같은 조건으로 SELECT를 먼저 돌려 대상 행을 확인하는 것이 기본 안전 습관입니다. 또한 PostgreSQL의
UPDATE는 MVCC 특성상 dead tuple을 남기므로, 갱신이 잦은 테이블은 AUTOVACUUM 설정을 확인하고
필요하면 수동 VACUUM을 실행해야 합니다.
UPDATE posts
SET published = true
WHERE user_id = 42;행 하나만 바꾼다고 생각하고 실행했는데 조건이 넓으면 여러 행이 함께 바뀝니다. 특히 운영 데이터에서는 먼저 같은 조건으로 SELECT COUNT(*)를 확인하는 습관이 안전합니다.
참고 링크
3 sources