외부 시스템과의 인터페이스를위한 복잡한 기본 키와 내부 사용을위한 빠르고 작은 불투명 기본 키가있는 시스템이 있습니다. 예를 들어, 외부 키는 (주어진 이름 (varchar), 성 (varchar), 우편 번호 (char)와 같은) 복합 값일 수 있으며 내부 키는 정수 ("고객 ID")가 될 수 있습니다. 내가 외부 키를 사용하여 들어오는 요청을 수신 할 때일반 SQL에서 새 행을 동시에 검색 (선택) 또는 작성 (삽입)
, 나는 내부 키를 볼 필요 - 여기에 까다로운 부분입니다 - 이미 주어진 외부 ID에 대한이없는 경우 새로운 내부 키를 할당 .
분명히 한 번에 하나의 클라이언트 만 데이터베이스에 대화하는 것이면 좋습니다. SELECT customer_id FROM customers WHERE given_name = 'foo' AND ...
, 그 다음 INSERT INTO customers VALUES (...)
값을 찾지 못했습니다. 그러나 외부 시스템에서 동시에 들어오는 요청이 많고 이전에 전례가없는 고객이 한꺼번에 많은 요청을받을 수있는 경우 여러 클라이언트가 새로운 행을 시도 할 수있는 경쟁 조건이 있습니다.
기존 행을 수정하는 것이 쉽습니다. 단순히 SELECT FOR UPDATE
, 먼저 UPDATE
을 수행하기 전에 적절한 행 수준 잠금을 획득하십시오. 그러나이 경우 행이 아직 존재하지 않기 때문에 잠글 수있는 행이 없습니다!
지금까지 몇 가지 솔루션을 마련했지만, 그들 각각은 꽤 중요한 문제가 있습니다
- 캐치
INSERT
에 오류가 상단에서 전체 트랜잭션을 다시 시도하십시오. 이것은 거래가 12 명의 고객을 포함하는 경우, 특히 들어오는 데이터가 매번 다른 고객의 주문에 대해 잠재적으로 이야기하고있는 경우 문제가됩니다. 매번 다른 고객에게 충돌이 발생하는 상호 회귀 교착 상태 루프에 걸릴 수 있습니다. 재 시도 시도 사이의 지수 대기 시간을 사용하여이를 완화 할 수 있지만 충돌을 처리하는 데 시간이 오래 걸리고 비용이 많이 듭니다. 또한 모든 것이 다시 시작될 수 있어야하므로 응용 프로그램 코드가 상당히 복잡해집니다. - 세이브 포인트를 사용하십시오.
SELECT
전에 세이브 포인트를 시작하고INSERT
에서 오류를 catch 한 다음 세이브 포인트 및SELECT
으로 다시 롤백하십시오. 세이브 포인트는 완전히 이식 가능하지 않으며, 의미와 기능이 데이터베이스간에 조금씩 다르다. 내가 알아 차 렸던 가장 큰 차이는 때로는 둥지처럼 보이기도하고 때로는 그렇지 않기 때문에 피할 수 있다면 좋을 것입니다. 이것은 막연한 인상 일뿐입니다. 정확하지 않습니까? 저장 점은 표준화 되었는가, 아니면 적어도 실질적으로 일관성이 있는가? 또한 저장 지점을 사용하면 트랜잭션에서 작업을 병렬로 수행하기가 어려워집니다. 롤백 할 작업의 양을 정확히 알 수 없기 때문에 저장해야 할 수도 있습니다. - LOCK 문 (oraclemysqlpostgres)을 사용하는 테이블 수준 잠금과 같은 일부 글로벌 잠금을 가져옵니다. 이것은 분명히 이러한 작업을 느리게하고 잠금 경합이 많이 발생하므로이를 피하는 것이 좋습니다.
- 보다 세부적이지만 데이터베이스 관련 잠금을 획득합니다. 나는 다른 데이터베이스 (이 함수는 심지어 "
pg_
"로 시작하는 경우도)에서 지원되지 않으므로 Postgres's way of doing this에만 익숙하므로 이식성 문제가 있습니다. 또한 포스트 그레스의 방법은 내가 키를 정수형으로 변환해야 할 필요가 있는데, 이는 깔끔하게 적합하지 않을 수 있습니다. 가상의 물건에 대한 자물쇠를 잡는 좋은 방법이 있습니까?
이것은 데이터베이스에서 공통적 인 동시성 문제가 될 것으로 보이지만 많은 리소스를 찾을 수 없었습니다. 아마 내가 정규 표현을 모르기 때문에. 태그가 추가 된 데이터베이스 중 일부에서 간단한 구문을 사용하여이 작업을 수행 할 수 있습니까?
upsert/merge 문? –
customerid가 외부 키의 일부인 경우 문제가 표시되지 않습니다. – dkretz
질문이 ** 단지 ** MySql이면 ** INSERT ... ON DUPLICATE KEY를 사용하는 것이 좋습니다. (선택적으로'customer_id = LAST_INSERT_ID (customer_id) '를 사용하여 행 customer_id를 검색) –