2012-12-20 3 views
2

,경쟁 조건이 무엇을 달성하려고하는 것은 간단한데

DB 유형 :의 MyISAM
테이블 구조 : card_id, 상태
쿼리 : 테이블에서 사용되지 않는 card_id을 선택하고 같은 행을 설정 "익숙한".

두 쿼리가 동시에 실행되고 상태가 업데이트되기 전에 동일한 card_id가 두 번 반입되는 경쟁 조건이 있습니까?

이미 일부 검색을 수행했습니다. 그것은 잠금 테이블은 해결책이지만, 그것은 나에게 과도한이고 잠금 권한이 필요합니다.

아이디어가 있으십니까?

감사합니다.

+0

일반적인 db 사용자의 경우 'LOCK PRIVILEGE'를 피하려면 실행을 위해 사용자 컨텍스트를 전환 할 수 있기 때문에 STORED PROCEDURE를 사용하면됩니다. –

+0

답변을 업데이트했습니다. 나는 사용자 변수를 사용하여 업데이트 된 행의'cardid' 값을 반환하는 예제를 제공했습니다. – spencer7593

답변

2

실제로 실행중인 명령문에 따라 다릅니다.

평범한 이전 UPDATE 문에 대한 MyISAM 테이블에 대한 MySQL은 전체 테이블에 대한 잠금을 획득하므로 두 세션 사이에 "경쟁"조건이 없습니다. 한 세션은 잠금이 해제 될 때까지 기다린 다음 자체 업데이트로 진행합니다 (또는 지정된 시간 동안 대기하고 "제한 시간"을 지정하여 중단합니다).

그러나 두 번째 세션 테이블에 대해 SELECT를 실행하고, 업데이트 할 행의 식별자를 검색하며, 두 세션 모두 동일한 행 식별자를 검색 한 다음 두 세션이 동일한 행을 업데이트하려고 시도하면 그렇습니다. 정말로 고려해야 만합니다.

이 조건이 해결되지 않으면 기본적으로 "마지막 업데이트가 이깁니다"문제가되고 두 ​​번째 세션은 이전 업데이트에서 변경된 내용을 (잠재적으로) 덮어 씁니다.

응용 프로그램에 적용 할 수없는 상황 인 경우 다른 디자인을 사용하거나 두 번째 업데이트가 첫 번째 업데이트에서 적용된 업데이트를 덮어 쓰지 못하도록하는 메커니즘을 사용하여 해결해야합니다.

하나의 접근법은 먼저 LOCK TABLES 문을 사용하여 테이블에 단독 잠금을 얻은 다음이 식별자를 얻기 위해 SELECT를 실행 한 다음 UPDATE를 실행하여 마지막으로 UNLOCK TABLES 문을 사용하여 잠금을 해제합니다.

저용량, 낮은 동시성 응용 프로그램에 대해 실행 가능한 접근 방식입니다. 하지만 몇 가지 중요한 단점이 있습니다. 주요 관심사는 성능 병목 현상을 일으킬 수있는 단일 리소스에서 얻는 배타적 잠금 때문에 동시성이 감소된다는 것입니다.

또 다른 대안은 "낙관적 인 잠금"이라고하는 전략입니다. ("비관적 잠금"이라고 할 수있는 앞에서 설명한 방법과 반대입니다.)

"낙관적 인 잠금"전략의 경우 추가 "카운터"열이 테이블에 추가됩니다. 테이블의 행에 업데이트가 적용될 때마다 해당 행의 카운터가 1 씩 증가합니다.

이 "카운터"열을 사용하려면 쿼리에서 나중에 업데이트 될 행 (또는 나중에 업데이트 될 수있는 행)을 검색 할 때 해당 쿼리는 카운터 열의 값도 검색합니다.

UPDATE를 시도하면 명령문은 행의 "counter"열의 현재 값을 이전에 검색 한 카운터 열의 값과 비교합니다. (우리는 단지 UPDATE 문의 WHERE 절)에서 예 (술어를 포함한다. 예를 들어,

UPDATE mytable 
    SET counter = counter + 1 
    , col = :some_new_value  
WHERE id = :previously_fetched_row_identifier 
    AND counter = :previously_fetched_row_counter 

다른 세션이 우리가 언젠가 시간 사이 (갱신하려는 행에 업데이트를 적용하고 있다면 우리의 세션이 행을 검색하고 세션이 업데이트를 시도하기 전에) 해당 행의 "카운터"열의 값이 변경됩니다.

UPDATE 문의 술어가이를 확인하고 "counter"가 변경되면 업데이트가 적용되지 않을 것입니다. 그런 다음이 조건을 감지 할 수 있습니다 (즉, 영향을받는 행 수는 1이 아닌 0이 될 것입니다). 세션이 적절한 조치를 취할 수 있습니다. ("Hey ! 일부 어 세션에서 업데이트 할 행을 업데이트했습니다! ")

"낙관적 인 잠금 "전략을 구현하는 방법에 대한 몇 가지 좋은 글이 있습니다.

일부 ORM 프레임 워크 (예 : Hibernate, JPA)는 이러한 유형의 잠금 전략을 지원합니다.

UPDATE ... 
    SET status = 'used' 
WHERE status = 'unused' 
    AND ROWNUM = 1 
RETURNING card_id INTO ... 

다른 RDBMS (예를 들어, 오라클) 기능의 종류를 제공 할 :


불행하게도, MySQL은 같은 UPDATE 문에서 RETURNING 절에 대한 지원을 제공하지 않습니다. UPDATE 문의 해당 기능을 사용하면 status = 'unused' 행을 찾은 다음 status = 'used' 값을 변경하고 3) 행의 card_id (또는 원하는 모든 열)을 반환하기 위해 UPDATE 문을 실행할 수 있습니다. 방금 업데이트했습니다.

SELECT를 실행 한 다음 별도의 UPDATE를 실행하여 SELECT와 UPDATE 사이의 행을 업데이트하는 다른 세션의 잠재성에 대한 문제를 해결합니다.

그러나 RETURNING 절은 MySQL에서 지원되지 않습니다. 그리고 MySQL 내에서이 유형의 기능을 에뮬레이션 할 수있는 확실한 방법을 찾지 못했습니다.


이 작동 할 수 있습니다 당신

나는 이전에 사용자 변수를 사용하여이 방법 (나는이 주변에 연주했던 그 위에 언급했다. 나는 어쩌면 내가 필요로 생각을 포기하는 이유 완전히 확실하지 않다 좀 더 일반적으로, 하나 이상의 행을 업데이트하고 일련의 id 값을 반환하거나 사용자 변수의 동작에 대해 보장되지 않는 무언가가있을 수 있습니다. (그런 다음 다시 신중하게 작성된 사용자 변수 만 참조합니다. SELECT 문, DML에서 사용자 변수를 사용하지 않습니다. 동작을 보장 할 수 없기 때문일 수 있습니다.)

정확히 한 행에 관심이 있기 때문에

, 세 문장의 순서가 당신을 위해 작동 할 수 있습니다

SELECT @id := NULL ; 

UPDATE mytable 
    SET card_id = (@id := card_id) 
    , status = 'used' 
WHERE status = 'unused' 
LIMIT 1 ; 

SELECT ROW_COUNT(), @id AS updated_card_id ; 

그것은이 세 문장이 동일한 데이터베이스 세션에서 실행하는 것이 중요합니다 (즉, 데이터베이스 세션을 유지한다. 그것을 놓아 버리지 말고 새로운 것을 얻으십시오.)

먼저 테이블의 실제 card_id 값과 혼동하지 않는 값으로 사용자 변수 (@id)를 초기화합니다.)

다음으로, 우리는 하나의 UPDATE 문을 실행 (A SET @id := NULL 문은 SELECT 문이하는 것처럼. 그 결과를 반환하지 않고, 잘 작동) status = 'unused'이, 2) status 컬럼의 값을 변경 한 행을 찾을 수 'used'으로 설정하고 3) @id 사용자 변수의 값을 변경 한 행의 card_id 값으로 설정하십시오. 가능한 문자 집합 변환 문제를 피하려면 card_id 열을 정수가 아닌 정수 유형으로 지정하십시오.

다음으로 이전 UPDATE 문에서 변경된 행 수를 얻기 위해 ROW_COUNT() 함수 (이 값은 클라이언트 측에서 1이라는 것을 확인해야 함)에서 변경된 행의 card_id 값이 될 @id 사용자 변수의 값을 검색합니다.

+0

그런 답변을 해주셔서 감사합니다! –

+0

여기에 긴 설명을 추가하기가 어렵 기 때문에 내 질문에 답해야합니다. 내 대답을 확인하고 생각을 게시하십시오. 감사! –

1

나는이 질문을 게시 한 후 마지막에 언급 한 것과 정확히 같은 해결책을 생각했습니다. 나는 update 문을 사용했다. "update TABLE set status = 'used'여기서 status = 'unused'limit 1, 이는 테이블의 기본 ID를 반환하고이 기본 ID를 사용하여 cart_id를 얻을 수있다. 당신이 말했듯이 "MySQL은 전체 테이블에 대한 잠금을 얻습니다. 따라서 두 세션 사이에"경쟁 "조건이 없으므로이 문제가 해결됩니다. 그러나 왜 "MySQL은 스타일 선언문을 지원하지 않습니다."라고 말한 이유는 확실하지 않습니다.

+0

'UPDATE' 문의 리턴 값은 명령문의 영향을받는 행들의 수입니다. UPDATE 문 다음에 오는'mysql_info' (C API) 함수를 호출하면 일치하는 행의 수, 변경된 행의 수 및 경고의 수를 나타내는 문자열이 제공됩니다. MySQL에서 영향을받는 행의 기본 키 값을 반환하는 메커니즘이 있다면 매우 유용 할 몇 가지 상황이 있기 때문에 매우 시연에 관심이 있습니다. – spencer7593

+0

나는 내 대답을 조금 바꾸었다. "MySQL이 지원을 제공하지 않는다"고 말했을 때 Oracle에 지원되는 UPDATE 문의 RETURNING 절을 특별히 언급했습니다. 'UPDATE t SET c = 1 WHERE c = 0 LIMIT 1'형식의 문장은 업데이트 된 행을 확인하는 확실한 방법이없는 한, 정확히 원하는대로 수행합니다. (나는 사용자 변수와 타임 스탬프 열을 가지고 놀았지만, 행을 식별하는 신뢰할 수있는 확실한 방법을 찾지 못했습니다. – spencer7593

관련 문제