동일한 기능에 대한 동시 호출 : 교착 상태는 어떻게 발생합니까?


15

new_customer웹 응용 프로그램에서 내 함수를 초당 여러 번 (세션 당 한 번만) 호출합니다. 가장 먼저하는 일은 customer테이블을 잠그는 것입니다 ( '존재하지 않는 경우 삽입'-간단한 변형 upsert).

문서대한 나의 이해는 new_customer이전의 모든 호출이 완료 될 때까지 다른 호출 이 단순히 대기해야한다는 것입니다.

LOCK TABLE은 충돌하는 잠금이 해제 될 때까지 대기하면서 테이블 수준 잠금을 얻습니다.

왜 대신 교착 상태가 발생합니까?

정의:

create function new_customer(secret bytea) returns integer language sql 
                security definer set search_path = postgres,pg_temp as $$
  lock customer in exclusive mode;
  --
  with w as ( insert into customer(customer_secret,customer_read_secret)
              select secret,decode(md5(encode(secret, 'hex')),'hex') 
              where not exists(select * from customer where customer_secret=secret)
              returning customer_id )
  insert into collection(customer_id) select customer_id from w;
  --
  select customer_id from customer where customer_secret=secret;
$$;

로그 오류 :

BST 세부 정보 : 프로세스 12380은 데이터베이스 12141의 16438 관계에서 ExclusiveLock을 기다립니다. 프로세스 12379에 의해 차단되었습니다.
        프로세스 12379는 데이터베이스 12141의 관계 16438에서 ExclusiveLock을 대기합니다. 프로세스 12380에 의해 차단되었습니다.
        프로세스 12380 : new_customer (decode ($ 1 :: text, 'hex')) 선택
        프로세스 12379 : new_customer (decode ($ 1 :: text, 'hex')) 선택
2015-07-28 08:02:58 BST 힌트 : 쿼리 세부 정보는 서버 로그를 참조하십시오.
2015-07-28 08:02:58 BST 컨텍스트 : SQL 함수 "new_customer"문 1
2015-07-28 08:02:58 BST 진술 : new_customer (decode ($ 1 :: text, 'hex') 선택)

관계:

postgres=# select relname from pg_class where oid=16438;
┌──────────┐
 relname  
├──────────┤
 customer 
└──────────┘

편집하다:

나는 간단하고 재현 가능한 테스트 사례를 얻었습니다. 나에게 이것은 일종의 경쟁 조건으로 인해 버그처럼 보입니다.

개요:

create table test( id serial primary key, val text );

create function f_test(v text) returns integer language sql security definer set search_path = postgres,pg_temp as $$
  lock test in exclusive mode;
  insert into test(val) select v where not exists(select * from test where val=v);
  select id from test where val=v;
$$;

bash 스크립트는 두 개의 bash 세션에서 동시에 실행됩니다.

for i in {1..1000}; do psql postgres postgres -c "select f_test('blah')"; done

오류 로그 (보통 1000 개가 넘는 소수의 교착 상태) :

2015-07-28 16:46:19 BST ERROR:  deadlock detected
2015-07-28 16:46:19 BST DETAIL:  Process 9394 waits for ExclusiveLock on relation 65605 of database 12141; blocked by process 9393.
        Process 9393 waits for ExclusiveLock on relation 65605 of database 12141; blocked by process 9394.
        Process 9394: select f_test('blah')
        Process 9393: select f_test('blah')
2015-07-28 16:46:19 BST HINT:  See server log for query details.
2015-07-28 16:46:19 BST CONTEXT:  SQL function "f_test" statement 1
2015-07-28 16:46:19 BST STATEMENT:  select f_test('blah')

편집 2 :

@ypercube 는 함수 외부의 변형제안했습니다lock table .

for i in {1..1000}; do psql postgres postgres -c "begin; lock test in exclusive mode; select f_test('blah'); end"; done

흥미롭게도 교착 상태가 제거됩니다.


2
동일한 트랜잭션에서 해당 기능을 시작하기 전에 customer약한 잠금을 잡는 방식으로 사용됩니까? 그러면 잠금 업그레이드 문제 일 수 있습니다.
Daniel Vérité

2
나는 이것을 설명 할 수 없다. 다니엘은 요점이있을 수 있습니다. pgsql-general에서 이것을 올릴 가치가 있습니다. 어느 쪽이든, 다가오는 Postgres 9.5의 UPSERT 구현에 대해 알고 있습니까? 그것을보고 Depesz.
Erwin Brandstetter

2
잠금이 tx end에서 해제되기 때문에 동일한 세션뿐만 아니라 동일한 트랜잭션 내에서 의미합니다. @ alexk의 대답은 내가 생각한 것이지만 tx가 함수로 시작하고 끝나는 경우 교착 상태를 설명 할 수 없습니다.
Daniel Vérité

1
@ Erwin 당신은 의심 할 여지없이 pgsql-bugs에 게시 한 답변에 관심을 가질 것입니다 :)
Jack은 tryansweranswers.xyz를 시도합니다

2
정말 흥미 롭습니다. 예상대로 작동하는 유사한 plpgsql 사례를 기억하기 때문에 plpgsql에서도 작동한다는 것을 이해합니다.
Erwin Brandstetter

답변:


10

나는이 게시 를 pgsql-버그거기에 응답 톰 레인에서이 처리되는 방법 SQL 언어 기능의 메커니즘에 의해 위장 잠금 에스컬레이션 문제, 나타냅니다. 기본적으로에 의해 생성 된 잠금은 테이블의 독점 잠금 전에insert 얻 습니다 .

이것의 문제는 SQL 함수가 전체 함수 본문에 대해 구문 분석 (및 계획도, 코드를 확인하고 싶지 않다는 것)을 한 번에 수행한다는 것입니다. 이는 INSERT 명령으로 인해 LOCK 명령이 실제로 실행되기 전에 함수 본문 구문 분석 중에 "test"테이블에서 RowExclusiveLock을 획득 함을 의미합니다. 따라서 LOCK은 잠금 에스컬레이션 시도를 나타내며 교착 상태가 예상됩니다.

이 코딩 기술은 plpgsql에서는 안전하지만 SQL 언어 기능에서는 안전하지 않습니다.

구문 분석이 한 번에 하나의 명령문에서 발생하도록 SQL 언어 함수를 다시 구현하는 것에 대한 논의가 있었지만 해당 방향으로 발생하는 일에 대해 숨을 쉬지 마십시오. 그것은 누구에게도 우선 순위가 높은 것으로 보이지 않습니다.

톰 레인

또한 래핑 plpgsql 블록 (@ypercube에서 제안한 대로) 에서 함수 외부에서 테이블 잠그면 교착 상태가 발생하지 않는 이유에 대해 설명합니다 .


3
좋은 점 : ypercube는 실제로 plpgsql 블록 과 동일 하지 않은 함수 외부 의 명시 적 트랜잭션 에서 일반 SQL의 잠금을 테스트했습니다 .
Erwin Brandstetter

1
맞아, 내 나쁜 우리가 시도한 다른 것과 혼동되고 있다고 생각 합니다 (교착 상태를 막지 못했습니다).
잭 topanswers.xyz 시도라고

4

new_customer를 호출하기 전에 다른 명령문을 실행하고이 명령문이 충돌하는 잠금 EXCLUSIVE(기본적으로 고객 테이블의 모든 데이터 수정)을 획득한다고 가정하면 설명은 매우 간단합니다.

간단한 예제로 문제를 재현 할 수 있습니다 (함수도 포함하지 않음).

CREATE TABLE test(id INTEGER);

첫 세션 :

BEGIN;

INSERT INTO test VALUES(1);

두 번째 세션

BEGIN;
INSERT INTO test VALUES(1);
LOCK TABLE test IN EXCLUSIVE MODE;

첫 세션

LOCK TABLE test IN EXCLUSIVE MODE;

첫 번째 세션은 삽입을 수행 할 때 ROW EXCLUSIVE테이블에 대한 잠금을 획득 합니다. 한편 세션 2는 ROW EXCLUSIVE잠금을 시도하고 EXCLUSIVE잠금 을 얻으려고 시도합니다 . EXCLUSIVElock이와 충돌 하므로 어느 시점에서 첫 번째 세션을 기다려야합니다 ROW EXCLUSIVE. 마지막으로, 첫 번째 세션은 상어를 뛰어 넘어 EXCLUSIVE잠금을 시도 하지만 잠금은 순서대로 획득되므로 두 번째 세션 후에 대기합니다. 이것은 차례로 첫 번째 것을 기다리면서 교착 상태를 만듭니다.

DETAIL:  Process 28514 waits for ExclusiveLock on relation 58331454 of database 44697822; blocked by process 28084.
Process 28084 waits for ExclusiveLock on relation 58331454 of database 44697822; blocked by process 28514

이 문제에 대한 해결책은 일반적으로 트랜잭션에서 가장 먼저 잠금을 가능한 빨리 획득하는 것입니다. 반면, PostgreSQL 워크로드는 매우 드문 경우에만 잠금이 필요하므로 upsert 수행 방식을 다시 생각하는 것이 좋습니다 (이 기사 http://www.depesz.com/2012/06/10 참조) . / why-is-upsert-so-comlicated / ).


2
이것은 모두 흥미롭지 만 db 로그의 메시지는 다음과 같습니다. Process 28514 : select new_customer(decode($1::text, 'hex')); Process 28084 : BEGIN; INSERT INTO test VALUES(1); select new_customer(decode($1::text, 'hex'))Jack이 방금 얻은 동안 Process 12380: select new_customer(decode($1::text, 'hex')) Process 12379: select new_customer(decode($1::text, 'hex'))-함수 호출이 두 트랜잭션에서 첫 번째 명령임을 나타냅니다 (내가 빠진 경우가 아니면).
Erwin Brandstetter

감사합니다. 귀하의 의견에 동의하지만이 경우에는 그 원인이 아닌 것 같습니다. 내가 질문에 추가 한 최소한의 테스트 사례에서 더 명확합니다 (직접 시도해 볼 수 있음).
잭 topanswers.xyz 시도라고

2
실제로 메커니즘은 미묘 하지만 잠금 에스컬레이션에 대해 옳다는 것이 밝혀졌습니다 .
잭 topanswers.xyz 시도라고
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.