기술나눔

PostgreSQL에서 데이터 동시 업데이트에 대한 충돌 해결을 처리하는 방법은 무엇입니까?

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

아름다운 구분선

포스트그레스큐엘


데이터베이스 동시 운영 환경에서는 동일한 데이터를 동시에 업데이트하려는 여러 트랜잭션이 충돌을 일으킬 수 있습니다. PostgreSQL은 데이터 일관성과 무결성을 보장하기 위해 이러한 동시 업데이트 충돌을 처리하는 일련의 메커니즘을 제공합니다.

아름다운 구분선

1. 동시 업데이트 충돌 시나리오

두 개 이상의 트랜잭션이 동시에 동일한 데이터 행을 수정하려고 시도하면 동시 업데이트 충돌이 발생할 수 있습니다. 일반적인 시나리오는 다음과 같습니다.

  1. 같은 행의 다른 열을 동시에 수정
  2. 동일한 열의 다른 값을 동시에 업데이트

아름다운 구분선

2. PostgreSQL의 동시성 제어 메커니즘

PostgreSQL은 주로 MVCC(Multiversion Concurrency Control)를 사용하여 동시 트랜잭션을 처리합니다. MVCC를 사용하면 트랜잭션이 다른 트랜잭션의 읽기 작업을 차단하기 위해 잠그지 않고도 격리 수준 요구 사항을 충족하는 데이터 버전을 읽을 수 있습니다. 그러나 쓰기 작업 중에 충돌이 계속 발생할 수 있습니다.

(1) 차단 메커니즘

PostgreSQL은 여러 유형의 잠금을 사용하여 데이터에 대한 동시 액세스를 제어합니다. 일반적인 잠금 유형은 다음과 같습니다.

  1. 공유 잠금: 다른 트랜잭션이 공유 잠금을 획득할 수 있도록 허용하지만 배타적 잠금을 획득할 수는 없습니다. 일반적으로 읽기 작업에 사용됩니다.
  2. 배타적 잠금: 다른 트랜잭션이 쓰기 작업에 자주 사용되는 모든 유형의 잠금을 획득하는 것을 방지합니다.

잠금 세분성은 행 수준(행 수준), 페이지 수준(페이지 수준) 및 테이블 수준(테이블 수준)일 수 있습니다.

(2) 거래 격리 수준

PostgreSQL은 네 가지 트랜잭션 격리 수준을 지원합니다.

  1. 커밋되지 않은 읽기: 가장 낮은 격리 수준입니다. 트랜잭션은 다른 트랜잭션의 커밋되지 않은 데이터 수정 사항을 읽을 수 있으며, 이로 인해 더티 읽기, 반복 불가능 읽기, 팬텀 읽기 등의 문제가 발생할 수 있습니다.
  2. 커밋된 읽기: 트랜잭션은 더티 읽기를 방지하여 제출된 데이터만 읽을 수 있지만 반복 불가능한 읽기 및 팬텀 읽기는 여전히 발생할 수 있습니다.
  3. 반복 가능한 읽기: 트랜잭션 내에서 동일한 데이터를 여러 번 읽으면 동일한 결과를 얻게 되며 반복 불가능한 읽기는 방지되지만 팬텀 읽기가 발생할 수 있습니다.
  4. 직렬화 가능: 가장 높은 격리 수준으로 엄격한 동시성 제어를 통해 트랜잭션의 직렬 실행을 보장하고 더티 읽기, 반복 불가능한 읽기 및 팬텀 읽기를 방지합니다.

아름다운 구분선

3. 동시 업데이트 충돌 해결 방법

(1) 재시도 메커니즘

간단한 접근 방식은 충돌이 발생할 때 트랜잭션을 다시 시도하는 것입니다. 예는 다음과 같습니다:

DO
$$
DECLARE
    conflict_detected BOOLEAN := FALSE;
BEGIN
    LOOP
        -- 尝试执行更新操作
        UPDATE products SET price = 100 WHERE id = 1;

        -- 检查是否有冲突(例如,通过检查受影响的行数)
        IF NOT FOUND THEN
            conflict_detected := TRUE;
        ELSE
            EXIT;
        END IF;

        -- 若有冲突,等待一段时间并重试
        IF conflict_detected THEN
            PERFORM pg_sleep(1);
        END IF;
    END LOOP;
END;
$$;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

위의 예에서 업데이트 작업이 행에 영향을 주지 않으면(충돌이 있을 수 있음을 나타냄) 플래그가 설정되고 일정 시간 동안 기다린 후 다시 시도됩니다.

(2) 낙관적 동시성 제어 사용

낙관적 동시성 제어에서는 동시성 충돌이 거의 발생하지 않는다고 가정합니다. 이 방법은 데이터를 업데이트할 때 트랜잭션을 잠그지 않고 커밋할 때 다른 트랜잭션에 의해 데이터가 수정되었는지 확인합니다. 충돌이 없으면 트랜잭션이 성공적으로 커밋됩니다. 충돌이 있으면 트랜잭션이 롤백되고 필요에 따라 다시 시도됩니다.

-- 获取数据的初始版本
SELECT price AS original_price FROM products WHERE id = 1;

-- 进行业务处理和修改
UPDATE products SET price = 100 WHERE id = 1 AND price = original_price;
  • 1
  • 2
  • 3
  • 4
  • 5

위의 예에서 업데이트 작업은 다른 트랜잭션에 의해 데이터가 수정되지 않은 경우에만 성공합니다.

(3) 비관적 동시성 제어를 사용한다

비관적 동시성 제어는 동시성 충돌이 발생할 가능성이 있다고 가정하고 트랜잭션 실행 중에 필요한 잠금을 획득하여 잠재적으로 충돌할 수 있는 다른 트랜잭션을 차단합니다.

BEGIN;

-- 获取排他锁
LOCK TABLE products IN SHARE ROW EXCLUSIVE MODE;

-- 进行数据更新
UPDATE products SET price = 100 WHERE id = 1;

COMMIT;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

(4) 애플리케이션 버전 필드

데이터 변경 사항을 추적하려면 테이블에 버전 필드를 추가하세요.

CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    price DECIMAL(10, 2),
    version INT DEFAULT 0
);
  • 1
  • 2
  • 3
  • 4
  • 5

데이터를 업데이트할 때 버전 필드도 증가시킵니다.

UPDATE products SET price = 100, version = version + 1 WHERE id = 1 AND version = <expected_version>;
  • 1

업데이트의 영향을 받는 행 수가 0인 경우 예상 버전과 실제 버전이 일치하지 않아 충돌이 발생합니다.

(5) 타임스탬프 기반 충돌 해결

데이터의 마지막 수정 시간을 기록하려면 데이터의 각 행에 타임스탬프 필드를 추가하세요.

CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    price DECIMAL(10, 2),
    last_modified TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
  • 1
  • 2
  • 3
  • 4
  • 5

업데이트할 때 현재 트랜잭션에서 읽은 타임스탬프보다 오래된 타임스탬프가 있는 데이터만 업데이트하세요.

UPDATE products SET price = 100 WHERE id = 1 AND last_modified <= <read_timestamp>;
  • 1

아름다운 구분선

4. 실제 적용 시 고려사항

(1) 성능 영향

  1. 다양한 충돌 해결 방법은 데이터베이스 성능에 다양한 영향을 미칩니다. 예를 들어 차단을 사용하면 다른 트랜잭션이 대기하게 되어 시스템 차단 시간이 늘어나 동시성에 영향을 미칠 수 있습니다. 낙관적 동시성 제어는 충돌이 거의 발생하지 않을 때 더 잘 수행되지만, 충돌이 자주 발생하면 많은 수의 트랜잭션 재시도가 발생하여 전체 실행 시간이 늘어날 수 있습니다.
  2. 버전 필드 또는 타임스탬프 기반 방법을 적용하려면 버전 또는 타임스탬프 정보를 유지하고 업데이트 시 추가 판단 및 처리를 수행하기 위해 추가 저장 공간이 필요할 수 있습니다.

(2) 비즈니스 로직 적응성

  1. 특정 비즈니스 시나리오는 특정 충돌 해결 방법에 더 적합할 수 있습니다. 예를 들어, 비즈니스에서 데이터 일관성에 대한 요구 사항이 매우 높고 불일치를 허용할 수 없는 경우 비관적 동시성 제어 또는 직렬화 격리 수준이 더 나은 선택일 수 있습니다.
  2. 낙관적 동시성 제어는 충돌이 덜 빈번하고 응답 시간 요구 사항이 더 높은 시나리오에 더 적합할 수 있습니다.

(3) 데이터 유통 및 접근 패턴

  1. 데이터에 대한 액세스가 동시에 발생하고 여러 트랜잭션이 동시에 동일한 데이터 행에 액세스하는 경우가 많으면 과도한 차단 및 충돌을 피하기 위해 충돌 해결 방법을 더 신중하게 선택해야 합니다.
  2. 데이터 분포가 비교적 균일하고 충돌 확률이 낮은 상황에서는 낙관적 동시성 제어와 같은 비교적 간단하고 효율적인 방법을 사용할 수 있습니다.

아름다운 구분선

5. 예시 분석

다음과 같은 온라인 상점의 재고 관리 시스템이 있다고 가정해 보겠습니다. inventory 품목의 재고 수량을 저장하는 테이블입니다.

CREATE TABLE inventory (
    product_id INT PRIMARY KEY,
    quantity INT,
    last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
  • 1
  • 2
  • 3
  • 4
  • 5

이제 두 개의 동시 트랜잭션이 있습니다.

거래 1:

BEGIN;
SELECT * FROM inventory WHERE product_id = 1;
-- 假设读取到的数量为 10
UPDATE inventory SET quantity = 5 WHERE product_id = 1 AND last_updated <= <read_timestamp>;
COMMIT;
  • 1
  • 2
  • 3
  • 4
  • 5

거래 2:

BEGIN;
SELECT * FROM inventory WHERE product_id = 1;
-- 假设也读取到的数量为 10
UPDATE inventory SET quantity = 8 WHERE product_id = 1 AND last_updated <= <read_timestamp>;
COMMIT;
  • 1
  • 2
  • 3
  • 4
  • 5

이 두 트랜잭션이 거의 동시에 실행되면 충돌이 발생할 수 있습니다.

타임스탬프 기반 충돌 해결을 사용하는 경우:

  1. 트랜잭션 1은 데이터를 읽을 때 현재 타임스탬프를 얻었습니다(T1)。
  2. 트랜잭션 2는 데이터를 읽을 때 약간 늦은 타임스탬프를 얻었습니다(T2)。

트랜잭션 1이 업데이트를 시도할 때 데이터를 읽은 이후 다른 트랜잭션이 데이터를 수정하지 않은 경우(예: last_updated <= T1), 업데이트가 성공했습니다.

트랜잭션 2가 업데이트를 시도할 때 데이터가 last_updated 그 이상T2(트랜잭션 2가 읽은 후 수정되었음을 나타냄) 업데이트가 실패하고 트랜잭션 2는 롤백하고 다시 시도하거나 비즈니스 로직에 따라 다른 처리를 수행하도록 선택할 수 있습니다.


아름다운 구분선

🎉相关推荐

포스트그레스큐엘