ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • UPDATE 전후 값을 한 번에 받고 싶다면: PostgreSQL 18 RETURNING
    Programming 2026. 9. 20. 10:52
    728x90
    반응형

    움푹 팬 점토 그릇과 매끈한 그릇 사이의 작은 화살표
    수정 전후 상태를 함께 보는 개념을 표현한 AI 생성 이미지이며 실제 작업 기록은 아닙니다.

    PostgreSQL 18에서 수정 전후 값이 필요하면 RETURNING의 old와 new를 명시하고 반환 행 수까지 함께 확인하세요.

    값을 바꾸기 전에 SELECT로 읽고, UPDATE한 뒤 다시 SELECT로 읽는 코드는 이해하기 쉽습니다. 하지만 조회 사이에 다른 작업이 같은 행을 바꿀 수 있습니다. 변경 전후를 보고하려던 두 조회가 실제로 이번 UPDATE가 다룬 행의 전후와 일치하는지 따로 고민해야 합니다.

    PostgreSQL 18에서는 RETURNING에서 old와 new를 명시해 변경 전후 값을 함께 받을 수 있습니다. 다만 반환값을 받았다는 사실이 트랜잭션의 최종 커밋이나 업무 전체 완료를 의미하지는 않습니다. 아래는 2026년 9월 13일 공식 문서 기준의 설명이며 실제 데이터베이스에서 UPDATE를 실행하지 않았습니다.

    RETURNING은 이번 명령이 다룬 행의 값을 돌려줍니다

    RETURNING은 INSERT, UPDATE, DELETE, MERGE에서 사용할 수 있습니다. UPDATE의 기본 반환값은 수정 후 행의 값이며, 18에서는 old.quantity, new.quantity처럼 전후를 구분해 요청할 수 있습니다. 차이 계산도 반환식에 넣을 수 있습니다. 수정한 행의 값 반환

    이 기능은 별도 조회를 줄이고 이번 변경에서 얻어야 하는 값을 명령과 함께 표현하는 데 도움이 됩니다. 단순히 화면에 보여 주던 값을 “이전 값”으로 재사용하는 것과는 다릅니다.

    다만 old를 사용자가 예전에 조회한 값과 같은 것으로 생각하지 않습니다. 동시 수정이 있다면 실제 명령이 처리하는 행의 상태가 달라질 수 있습니다. PostgreSQL의 기본 Read Committed에서는 다른 수정의 완료를 기다린 뒤 갱신된 행에 조건을 다시 적용하는 경우가 있습니다. 동시 UPDATE와 조건 재검사

    그래서 RETURNING을 썼으니 잠금·격리 수준·동시 수정 조건을 더는 생각하지 않아도 된다는 결론은 틀립니다. 무엇을 변경할 수 있는지는 WHERE와 트랜잭션 설계에, 어떤 값을 받을지는 RETURNING에 각각 명시합니다.

    가상의 두 품목으로 한 행과 0행을 비교합니다

    설명용 재고 표에는 A 수량 2, B 수량 0이 있다고 하겠습니다. 요구는 수량이 1 이상인 품목에서만 하나를 줄이는 것입니다. sku는 기본 키이므로 한 품목이 여러 행에 중복되지 않는 조건입니다.

    다음은 PostgreSQL 18의 새 시험 연결에서 읽을 예시입니다. 진행 중인 다른 트랜잭션이나 같은 임시 테이블이 없는 환경을 전제로 합니다. 기존 운영 테이블을 대상으로 실행하지 않습니다.

    BEGIN;
    
    CREATE TEMP TABLE b40_inventory (
        sku text PRIMARY KEY,
        quantity integer NOT NULL CHECK (quantity >= 0)
    );
    
    INSERT INTO b40_inventory (sku, quantity)
    VALUES ('A', 2), ('B', 0);
    
    UPDATE b40_inventory
    SET quantity = quantity - 1
    WHERE sku = 'A' AND quantity >= 1
    RETURNING sku,
              old.quantity AS before_quantity,
              new.quantity AS after_quantity,
              new.quantity - old.quantity AS delta;
    
    UPDATE b40_inventory
    SET quantity = quantity - 1
    WHERE sku = 'B' AND quantity >= 1
    RETURNING sku,
              old.quantity AS before_quantity,
              new.quantity AS after_quantity;
    
    ROLLBACK;
    

    첫 UPDATE는 A의 2에서 1로 바뀌는 값과 차이 -1을 한 행으로 돌려주는 관계를 기대합니다. 둘째 UPDATE는 B의 수량 조건이 맞지 않아 반환할 행이 없는 관계입니다. 실제 SQL 실행 로그를 옮긴 것이 아니라 입력과 조건의 의미를 설명한 예시입니다.

    마지막 ROLLBACK은 이 예시의 변경을 확정하지 않기 위해 넣었습니다. 트랜잭션 안에서 만든 임시 테이블도 되돌려지므로, 그 뒤에 같은 테이블을 계속 조회할 수 있다고 가정하지 않습니다.

    반환 0행은 SQL 오류와 다른 상태입니다

    PostgreSQL은 UPDATE 대상이 0행인 것을 SQL 오류로 취급하지 않습니다. 반환 행이 없다는 사실과 쿼리 실행 자체가 실패했다는 사실을 구분해야 합니다. UPDATE의 결과와 행 수

    앞의 예시에서는 B가 있고 수량이 0이라고 입력을 고정했으므로 이유를 알 수 있습니다. 실제 서비스에서 같은 형태의 UPDATE가 0행을 반환하면 대상이 없는지, 수량 조건이 맞지 않는지, 다른 조건에서 제외됐는지 결과 하나만으로 확정할 수 없습니다.

    권한이나 행 보안 정책에 따라 보이거나 수정할 수 있는 행도 달라질 수 있습니다. 확인이 필요하다고 관리자 권한으로 무조건 우회하거나, 사용자에게 접근할 수 없는 행의 존재를 드러내는 응답을 만들지 않습니다. 행 보안 정책의 적용 범위

    따라서 애플리케이션의 응답도 명시적으로 나눕니다. 한 행이 필요한 작업에서 0행이면 성공으로 채우지 않고 정한 업무 거절·충돌 경로로 처리합니다. 예외가 발생했다면 그 오류를 별도로 다룹니다. old 값을 모른다고 0을 만들어 넣는 방식은 피합니다.

    한 행을 기대했다면 여러 행 반환도 점검합니다

    RETURNING은 실제로 갱신한 각 행에 대해 결과를 계산합니다. WHERE가 넓거나 키가 유일하지 않으면 여러 행을 수정할 수 있습니다. 반환된 첫 행만 읽고 나머지를 버리면 예상보다 많은 변경을 놓칠 수 있습니다.

    예시에서는 sku의 기본 키로 최대 한 행이라는 조건을 드러냈습니다. 실제 구현에서도 그 조건이 스키마와 쿼리 양쪽에서 유지되는지 확인합니다. 화면에서 한 항목을 선택했다는 사실만으로 UPDATE가 한 행에만 적용되는 것은 아닙니다.

    또 수정 행 수가 양수라고 값이 반드시 달라졌다는 뜻은 아닙니다. PostgreSQL은 조건에 맞아 UPDATE됐지만 값이 같게 남은 행도 갱신 행 수에 포함할 수 있습니다. old와 new가 같은지와 명령이 몇 행을 처리했는지는 별도의 검사입니다. 값이 같게 남은 UPDATE의 행 수

    이 구분은 “실제로 바뀐 경우에만 후속 작업” 같은 요구에서 중요합니다. 상태가 달라졌는지, 명령이 성공했는지, 몇 행을 대상으로 했는지를 한 성공 플래그로 합치지 않습니다.

    old와 new라는 이름의 의미를 명확하게 남깁니다

    UPDATE에서 이름을 붙이지 않은 quantity는 기본적으로 새 값입니다. 전후를 다루는 반환 목록에서는 old.quantity AS before_quantity, new.quantity AS after_quantity처럼 의미를 드러내면 호출부가 읽기 쉽습니다.

    필요하다면 RETURNING의 WITH 구문으로 OLD와 NEW에 출력용 별칭을 줄 수도 있습니다. 다만 별칭을 지정하면 기존 OLD·NEW 이름 대신 그 별칭을 사용해야 합니다. 대상 테이블의 별칭과 반환 행의 별칭도 구분해서 읽습니다. RETURNING 출력 별칭

    반환할 열은 필요한 것만 선택합니다. RETURNING *는 편리하지만 테이블에 새 열이 추가될 때 예상하지 않은 정보가 결과에 포함될 수 있습니다. 특히 변경 전 값에는 이전 개인 정보나 내부 상태가 들어 있을 수 있으므로 API 응답이나 로그로 그대로 흘리지 않습니다.

    호출부의 필드 이름도 바뀐 의미와 맞춥니다. 이전에 수정 후 수량 하나를 받던 코드가 전후 두 수량을 받게 됐다면 어떤 값을 화면과 후속 계산에 사용하는지 명시해야 합니다.

    트리거가 있는 테이블은 반환값도 그 조건에서 봅니다

    공식 문서는 대상 테이블에 트리거가 있으면 RETURNING에서 볼 수 있는 자료가 트리거에 의해 수정된 행의 값이라고 설명합니다. 따라서 SET 절에 쓴 식만으로 모든 반환값을 미리 계산할 수 있다고 가정하지 않습니다. 트리거와 반환 자료

    예시의 임시 테이블에는 별도 트리거를 넣지 않았습니다. 실제 테이블에서 기본값 보정이나 수정 시각 기록 같은 동작이 있다면 그 조건을 포함해 검사해야 합니다. UPDATE가 억제되는 트리거라면 조건에 맞는 행 수와 실제 처리 행 수가 달라질 수도 있습니다.

    다른 테이블을 FROM으로 연결해 수정할 때는 대상 한 행이 조인 결과 여러 행과 연결되지 않는지도 확인합니다. PostgreSQL 문서는 그런 경우 어느 조인 행이 사용될지 예측하기 어렵다고 경고합니다. old·new를 돌려받는다고 모호한 조인 조건이 자동으로 해결되는 것은 아닙니다. UPDATE FROM의 다중 대응 주의

    필요한 권한도 확인합니다. 수정 권한뿐 아니라 조건이나 반환식에서 읽는 열의 SELECT 권한이 관련될 수 있습니다. 예시가 관리자 연결에서 동작하는지와 실제 앱 역할로 동작하는지를 구분합니다.

    반환값을 받았어도 커밋 전일 수 있습니다

    트랜잭션 블록 안에서 UPDATE 결과를 받은 뒤 다른 작업이 실패하거나 ROLLBACK을 선택하면 그 변경은 확정되지 않습니다. 앞의 예시가 바로 전후 값을 확인할 수 있어도 마지막에는 되돌리는 구조입니다. 트랜잭션의 COMMIT과 ROLLBACK

    따라서 반환값을 받자마자 외부에 최종 성공을 알리는 흐름은 트랜잭션의 완료 시점과 맞춰야 합니다. DB 밖으로 보낸 메시지나 이미 수행한 외부 작업이 데이터베이스 ROLLBACK으로 자동 취소되는 것은 아닙니다.

    RETURNING 자체도 영구 감사 기록은 아닙니다. 클라이언트가 받은 값이며, 어떤 변경 이력을 어떤 트랜잭션과 보관 정책으로 남길지는 별도 설계입니다. 이 글은 감사 시스템이나 외부 메시지 전송을 구현한 안내가 아닙니다.

    사용하는 드라이버가 트랜잭션을 자동으로 시작하거나 커밋하는지도 확인합니다. SQL 화면의 한 문장과 앱 코드의 실제 커밋 경계가 다를 수 있습니다.

    전후 값, 행 수, 최종 완료를 따로 확인합니다

    검사 사례에는 한 행 변경, 조건 불일치 0행, 같은 값 대입, 예상 밖의 다중 대상, 트리거와 실제 역할의 권한, 마지막 ROLLBACK을 포함할 수 있습니다. PostgreSQL 18 문법이라는 버전 조건도 함께 고정합니다.

    그다음 반환 열의 의미와 실제 처리 행 수를 확인하고, 트랜잭션의 최종 결과를 따로 기록합니다. 이전에 SELECT로 읽은 값이 아니라 이번 UPDATE가 반환한 old와 new를 사용했는지 호출부까지 살펴야 합니다.

    RETURNING의 장점은 변경과 결과를 같은 명령에 담는 데 있습니다. 그 결과를 정확히 쓰려면 조건, 반환 행 수, 트리거·권한, 커밋 여부까지 함께 읽어야 합니다. 전후 수량이 보인다는 사실에서 끝내지 말고, 의도한 한 변경이 실제로 확정됐는지까지 확인하세요.

    728x90
    반응형
Designed by Tistory.