Postgres 업데이트 ... 제한 1


77

서버 상태 ( 'active', 'standby'등)와 같은 서버 클러스터에 대한 세부 정보가 포함 된 Postgres 데이터베이스가 있습니다. 활성 서버는 언제든지 대기로 장애 조치해야 할 수 있으며 특히 어떤 대기가 사용되는지는 중요하지 않습니다.

데이터베이스 쿼리가 대기 상태-JUST ONE-을 변경하고 사용할 서버 IP를 반환하려고합니다. 선택은 임의적 일 수 있습니다. 쿼리 상태에 따라 서버의 상태가 변경되므로 어떤 대기가 선택되었는지는 중요하지 않습니다.

쿼리를 하나의 업데이트로 제한 할 수 있습니까?

여기 내가 지금까지 가지고있는 것입니다 :

UPDATE server_info SET status = 'active' 
WHERE status = 'standby' [[LIMIT 1???]] 
RETURNING server_ip;

Postgres는 이것을 좋아하지 않습니다. 어떻게 다르게 할 수 있습니까?


코드에서 서버를 선택하고 제약이있는 곳에 추가하십시오. 또한 어쨌든 추가 조건 (가장 오래된, 최신, 가장 최근에 활성화 된, 가장 적은 부하, 동일한 DC, 다른 랙, 최소 오류)을 먼저 확인할 수 있습니다. 대부분의 장애 조치 프로토콜은 어쨌든 어떤 형태의 결정 성이 필요합니다.
eckes

@eckes 흥미로운 아이디어입니다. 필자의 경우 "코드에서 서버 선택"은 먼저 db에서 사용 가능한 서버 목록을 읽은 다음 레코드 업데이트하는 것을 의미 합니다 . 응용 프로그램의 많은 인스턴스가이 작업을 수행 할 수 있으므로 경쟁 조건이 있으며 원자 적 작업이 필요합니다 (5 년 전). 선택은 결정론적일 필요는 없습니다.
hugesupersuperman

답변:


125

동시 쓰기 액세스없이

CTE 에서 선택 사항을 구체화하고 의 FROM조항에 참여하십시오 UPDATE.

WITH cte AS (
   SELECT server_ip          -- pk column or any (set of) unique column(s)
   FROM   server_info
   WHERE  status = 'standby'
   LIMIT  1                  -- arbitrary pick (cheapest)
   )
UPDATE server_info s
SET    status = 'active' 
FROM   cte
WHERE  s.server_ip = cte.server_ip
RETURNING server_ip;

원래 여기에는 일반 하위 쿼리가 있었지만 Feike가 지적한 LIMIT것처럼 특정 쿼리 계획에 대해 회피 할 수 있습니다 .

플래너는 이상 중첩 루프를 실행하는 계획을 생성하도록 선택할 수 있습니다 LIMITing더 많은 원인이 서브 쿼리 UPDATEs보다 LIMIT예를 :

 Update on buganalysis [...] rows=5
   ->  Nested Loop
         ->  Seq Scan on buganalysis
         ->  Subquery Scan on sub [...] loops=11
               ->  Limit [...] rows=2
                     ->  LockRows
                           ->  Sort
                                 ->  Seq Scan on buganalysis

테스트 케이스 재생

위의 문제를 해결하는 방법은 LIMIT하위 쿼리를 자체 CTE 로 래핑하는 것입니다. CTE가 구체화되었으므로 중첩 루프의 다른 반복에서 다른 결과를 반환하지 않습니다.

또는 간단한 경우에 상관 관계 가 낮은 하위 쿼리 를 사용하십시오LIMIT 1. 더 간단하고 빠르게 :

UPDATE server_info
SET    status = 'active' 
WHERE  server_ip = (
         SELECT server_ip
         FROM   server_info
         WHERE  status = 'standby'
         LIMIT  1
         )
RETURNING server_ip;

동시 쓰기 액세스

이 모든 것에 대한 기본 격리 수준READ COMMITTED 을 가정 합니다. 엄격한 격리 수준 ( REPEATABLE READSERIALIZABLE)으로 인해 직렬화 오류가 발생할 수 있습니다. 보다:

동시 쓰기로드에서 FOR UPDATE SKIP LOCKED경쟁 조건을 피하기 위해 행을 잠 그려면 추가하십시오 . SKIP LOCKEDPostgres 9.5 에 추가되었으며 , 이전 버전은 아래를 참조하십시오. 매뉴얼 :

를 사용하면 SKIP LOCKED즉시 잠글 수없는 선택된 행을 건너 뜁니다. 잠긴 행을 건너 뛰면 데이터가 일관되지 않으므로 일반적인 작업에는 적합하지 않지만 대기열과 같은 테이블에 액세스하는 여러 소비자와의 잠금 경합을 피하는 데 사용할 수 있습니다.

UPDATE server_info
SET    status = 'active' 
WHERE  server_ip = (
         SELECT server_ip
         FROM   server_info
         WHERE  status = 'standby'
         LIMIT  1
         FOR    UPDATE SKIP LOCKED
         )
RETURNING server_ip;

규정 된 잠금 해제 된 행이 남아 있지 않으면이 쿼리에서 아무 행도 발생하지 않으며 (행이 업데이트되지 않음) 빈 결과가 나타납니다. 중요하지 않은 작업의 경우 완료된 것입니다.

그러나 동시 트랜잭션에 행이 잠겨있을 수 있지만 업데이트 ( ROLLBACK또는 다른 이유로)를 완료하지 마십시오 . 확인하려면 실행 최종 검사 :

SELECT NOT EXISTS (
   SELECT 1
   FROM   server_info
   WHERE  status = 'standby'
   );

SELECT잠긴 행도 볼 수 있습니다. 을 반환하지 않는 true하나 이상의 행이 계속 처리되고 있으며 트랜잭션을 계속 롤백 할 수 있습니다. (또는 새로운 행 사이에 추가되었습니다.) 조금, 다음 루프 두 단계를 기다립니다 ( UPDATE다시 더 행을 얻을 때까지, SELECT...) 당신이 얻을 때까지 true.

관련 :

없이 SKIP LOCKEDPostgreSQL을에 9.4 이상

UPDATE server_info
SET    status = 'active' 
WHERE  server_ip = (
         SELECT server_ip
         FROM   server_info
         WHERE  status = 'standby'
         LIMIT  1
         FOR    UPDATE
         )
RETURNING server_ip;

동일한 행을 잠 그려는 동시 트랜잭션은 첫 번째 행이 잠금을 해제 할 때까지 차단됩니다.

첫 번째 롤백 인 경우 다음 트랜잭션이 잠금을 수행하고 정상적으로 진행됩니다. 대기열에있는 다른 사람들은 계속 기다립니다.

처음 커밋 된 경우 WHERE조건이 다시 평가되고 TRUE더 이상 status변경 되지 않은 경우 CTE는 (어쩌면 놀랍게도) 행을 반환하지 않습니다. 아무 반응이 없습니다. 모든 트랜잭션을 업데이트 할 때 즉, 원하는 동작의 같은 행을 . 그러나 각 트랜잭션 업데이트 싶어하지 않을 경우 다음 행을 . 그리고 우리는 임의의 (또는 임의의 ) 행 을 업데이트하고 싶기 때문에 전혀 기다릴 필요가 없습니다.

권고 잠금을 사용 하여 상황을 차단 해제 할 수 있습니다 .

UPDATE server_info
SET    status = 'active' 
WHERE  server_ip = (
         SELECT server_ip
         FROM   server_info
         WHERE  status = 'standby'
         AND    pg_try_advisory_xact_lock(id)
         LIMIT  1
         FOR    UPDATE
         )
RETURNING server_ip;

이런 식으로, 아직 잠기지 않은 다음 행 이 업데이트됩니다. 각 트랜잭션마다 새로운 행이 생깁니다. 이 기술을 위해 Czech Postgres Wiki의 도움을 받았습니다 .

id고유 인 bigint열 (또는 같은 암시 적 캐스트와 모든 유형 int4또는 int2).

자문 잠금 장치가 동시에 데이터베이스에 여러 테이블에 대한 사용중인 경우로 명확하게 pg_try_advisory_xact_lock(tableoid::int, id)- id고유 인 integer여기.
이후 tableoidA는 bigint양이, 그것은 이론적으로 오버 플로우 수 있습니다 integer. 편집증이 충분하다면 (tableoid::bigint % 2147483648)::int대신에 사용하십시오-진정한 편집증에 이론적 인 "해시 충돌"을 남겨 두십시오 ...

또한 Postgres는 WHERE어떤 순서로든 조건을 자유롭게 테스트 할 수 있습니다. 이 를 테스트 pg_try_advisory_xact_lock()하고 잠금을 획득 하기 전에 status = 'standby' 관련이없는 행에 대한 자세한 자문 잠금을 초래할 수있는, status = 'standby'사실이 아니다. SO 관련 질문 :

일반적으로 이것을 무시할 수 있습니다. 규정 된 행만 잠기도록 하기 위해 위와 같은 CTE에 술어를 중첩 시키거나 OFFSET 0해킹이 있는 서브 쿼리를 중첩시킬 수 있습니다 (인라인 방지) . 예:

또는 (순차 스캔을 위해 저렴) CASE다음과 같은 명령문에 조건을 중첩시킵니다 .

WHERE  CASE WHEN status = 'standby' THEN pg_try_advisory_xact_lock(id) END

그러나CASE 트릭도에 인덱스를 사용하는 포스트 그레스를 유지하는 것입니다 status. 이러한 색인이 사용 가능한 경우, 추가 중첩이 필요하지 않습니다. 규정 된 행만 색인 스캔에서 잠 깁니다.

모든 호출에 인덱스가 사용되는지 확신 할 수 없으므로 다음과 같이 할 수 있습니다.

WHERE  status = 'standby'
AND    CASE WHEN status = 'standby' THEN pg_try_advisory_xact_lock(id) END

CASE논리적으로 중복이지만, 서버 논의 된 목적.

명령이 긴 트랜잭션의 일부인 경우 수동으로 해제 할 수 있고 해제해야하는 세션 수준 잠금을 고려하십시오. 따라서 잠긴 행을 마치면 바로 잠금을 해제 할 수 있습니다 : pg_try_advisory_lock()pg_advisory_unlock() . 매뉴얼 :

세션 레벨에서 획득 한 후에는 명시 적으로 해제되거나 세션이 종료 될 때까지 권고 잠금이 유지됩니다.

관련 :

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.