MySQL에서 낙관적 잠금을 올바르게 구현하는 방법


13

MySQL에서 어떻게 낙관적 잠금을 올바르게 구현합니까?

Google 팀은 아래에서 4 번을 수행해야한다고 판단했습니다. 그렇지 않으면 다른 스레드가 동일한 버전의 레코드를 업데이트 할 수있는 위험이 있지만 이것이 최선의 방법인지 확인하고 싶습니다.

  1. 낙관적 잠금을 사용하려는 테이블에 버전 필드를 작성하십시오 (예 : 열 이름 = "version").
  2. 선택시 버전 열을 포함하고 버전을 기록하십시오
  3. 레코드에 대한 후속 업데이트에서 업데이트 명령문은 "where version = X"를 발행해야합니다. 여기서 X는 # 2에서받은 버전이며 해당 업데이트 명령문 중 버전 필드를 X + 1로 설정해야합니다.
  4. 를 수행 SELECT FOR UPDATE우리는 우리가 우리가 갱신하려고하는 기록을 변경할 수있는 직렬화 그래서 업데이트하고자하는 기록에.

명확히하기 위해 동일한 시간 창에서 동일한 레코드를 선택하는 두 개의 스레드가 동일한 버전의 레코드를 가져와 동시에 레코드를 업데이트하려고 할 때 서로 겹쳐 쓰지 않도록 방지하려고합니다. 우리는 # 4를하지 않으면 두 스레드가 동시에 각각의 트랜잭션에 들어가더라도 (업데이트를 아직 발행하지 않은 경우) 업데이트 할 때 UPDATE를 사용할 두 번째 스레드가 될 가능성이 있다고 생각합니다 ... 버전 = X는 이전 데이터에서 작동합니다.

버전 필드 / 낙관적 잠금을 사용하는 경우에도 업데이트 할 때이 비관적 잠금을 수행해야한다고 생각하는 것이 맞습니까?


뭐가 문제 야? UPDATE를 사용하여 버전 번호를 늘리면 버전 번호가 읽을 때와 동일하지 않기 때문에 두 번째 UPDATE가 실패합니다.
AndreKR

확실한가요? 트랜잭션 격리 수준을 특정 설정으로 설정하지 않으면 실제로 다른 스레드가 업데이트되는 것을 알 수 없습니다. 둘 다 동시에 트랜잭션을 입력하면 두 번째 스레드는 업데이트를 수행 할 때 OLD 데이터를 매우 잘 볼 수 있습니다. MySQL은 Oracle만큼 ACID 분야에서 강력하지 않으므로 MySQL에서 더티 읽기 / 업데이트를 방지하는 낙관적 잠금을 구현하는 모범 사례 방법을 찾고 있습니다.
BestPractices

그러나 커밋 중에는 트랜잭션이 실패합니다.
AndreKR

이러한 상황을 처리하기 위해 업데이트를 선택하려고 할 것입니다. dev.mysql.com/doc/refman/5.0/en/innodb-consistent-read.html
BestPractices

@BestPractices 둘 다가 아니라 행 버전 관리로 낙관적 잠금 이 필요 합니다 SELECT ... FOR UPDATE . 자세한 내용은 답변을 참조하십시오.
Craig Ringer

답변:


17

개발자가 착각했습니다. 당신은 필요 하나 SELECT ... FOR UPDATE 또는 행이 둘을 버전.

사용해보십시오. 열기 세 MySQL의 세션 (A), (B)그리고 (C)동일한 데이터베이스에.

에서 (C)문제 :

CREATE TABLE test(
    id integer PRIMARY KEY,
    data varchar(255) not null,
    version integer not null
);
INSERT INTO test(id,data,version) VALUES (1,'fred',0);
BEGIN;
LOCK TABLES test WRITE;

모두에서 (A)(B)발행 UPDATE그 변화 테스트 및 설정 행 버전을, winner당신은 어느 것이 어느 세션을 볼 수 있도록 각각의 텍스트를 :

-- In (A):

BEGIN;
UPDATE test SET data = 'winnerA',
            version = version + 1
WHERE id = 1 AND version = 0;

-- in (B):

BEGIN;
UPDATE test SET data = 'winnerB',
            version = version + 1
WHERE id = 1 AND version = 0;

이제에서 (C), UNLOCK TABLES;잠금을 해제합니다.

(A)(B)행 잠금을 경주 할 것이다. 그들 중 하나가 이기고 자물쇠를 얻습니다. 다른 하나는 잠금 장치를 차단합니다. 자물쇠를 얻은 승자는 행 변경을 진행합니다. 가정 (A)이 승자 라고 가정하면 이제로 변경된 행 (여전히 커밋되지 않은 다른 트랜잭션에는 표시되지 않음)을 볼 수 있습니다 SELECT * FROM test WHERE id = 1.

이제 COMMIT승자 세션에서이라고 말합니다 (A).

(B)잠금이 설정되고 업데이트가 진행됩니다. 그러나 버전이 더 이상 일치하지 않으므로 행 수 결과에 의해보고 된대로 행이 변경되지 않습니다. 하나만 UPDATE영향을 미쳤으며 클라이언트 응용 프로그램은 UPDATE성공 및 실패를 명확하게 확인할 수 있습니다 . 추가 잠금이 필요하지 않습니다.

pastebin의 세션 로그를 참조하십시오 . 나는 mysql --prompt="A> "세션 간의 차이점을 쉽게 알기 위해 etc를 사용했습니다. 시간 순서대로 인터리브 된 출력을 복사하여 붙여 넣었으므로 완전히 원시 출력이 아니므로 복사하여 붙여 넣는 데 오류가 발생했을 수 있습니다. 직접 테스트 해보십시오.


당신이했다면 하지 행 버전 필드를 추가, 다음, 당신은 할 필요가 SELECT ... FOR UPDATE안정적으로 순서를 보장 할 수 있도록.

당신이 그것에 대해 생각한다면,의 데이터를 재사용하지 않고 즉시 수행 하거나 행 버전 관리를 사용 하는 경우 a SELECT ... FOR UPDATE완전히 중복 됩니다 . 는 어쨌든 잠금을 취할 것입니다. 다른 사람이 읽기와 후속 쓰기 사이의 행을 업데이트하면 버전이 더 이상 일치하지 않으므로 업데이트가 실패합니다. 그것이 낙관적 잠금 작동 방식입니다.UPDATESELECTUPDATE

목적 SELECT ... FOR UPDATE은 다음과 같습니다.

  • 교착 상태를 피하기 위해 잠금 순서를 관리합니다. 과
  • 행에서 데이터를 읽으려고 할 때 행 잠금 범위를 확장하려면 응용 프로그램에서 데이터를 변경하고 SERIALIZABLE격리 또는 행 버전 관리 를 사용하지 않고 원래 행을 기반으로하는 새 행을 작성하십시오 .

낙관적 잠금 (행 버전 관리) 및을 모두 사용할 필요는 없습니다 SELECT ... FOR UPDATE. 하나 또는 다른 것을 사용하십시오.


고마워 크레이그 당신은 옳았습니다. 개발자는 착각했습니다. 이 테스트를 실행 해 주셔서 감사합니다.
BestPractices

SQL 서버는 어떻습니까? 트랜잭션 격리 수준과 관계없이 항상 업데이트 된 행에서 잠금을 획득 했습니까?
plalx

@plalx 글쎄, 문서는 무엇을 말합니까? 이와 같은 대화식 테스트를 실행하면 어떻게됩니까?
Craig Ringer

@CraigRinger, B가 A 커밋 전에 A 업데이트 후에 잠금을 받으면 어떻게됩니까?
MengT

1
@MengT 그것은 불가능한 이유입니다.
Craig Ringer

0
UPDATE tbl SET owner = $me,
               id = LAST_INSERT_ID(id)
    WHERE owner = ''
    LIMIT 1;
$id = SELECT LAST_INSERT_ID();
Do some stuff (arbitrarily long time)...;
UPDATE  tbl SET owner = '' WHERE id = $id;

잠금 (테이블이 아닌 트랜잭션이 아님)이 필요하지 않거나 원하는 경우 :

  • 업데이트는 원 자성
  • LAST_INSERT_ID ()는 세션에 따라 다르므로 스레드로부터 안전합니다.
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.