programing

아직 존재하지 않는 InnoDB 행을 잠그려면 어떻게 해야 합니까?

magicmemo 2023. 9. 8. 21:21
반응형

아직 존재하지 않는 InnoDB 행을 잠그려면 어떻게 해야 합니까?

사용자 해당 이름을 새할 수 해야 ?SELECT그리고.INSERT진술?

마치 존재하지도 않는 줄을 걸어 잠그는 것처럼 말입니다.사용자 이름 "Foo"로 존재하지 않는 행을 잠금하여 데이터베이스에 존재하는지 확인하고 중단 없이 존재하지 않는 경우 데이터베이스에 삽입하고자 합니다.

사용하는 것은 알고 있습니다.LOCK IN SHARE MODE그리고.FOR UPDATE존재하지만 내가 아는 한 그것은 이미 존재하는 행에서만 작동합니다.이런 상황에서 어떻게 해야 할지 잘 모르겠습니다.

SELECT ... FOR UPDATE는 동시 세션/트랜지스터가 동일한 레코드를 삽입하는 것을 방지한다는 점에서 위의 답변은 맞지만, 이는 완전한 사실이 아닙니다.저는 현재 같은 문제와 싸우고 있으며, 다음과 같은 이유로 SELECT ... FOR UPDATE가 이 상황에서는 거의 소용이 없다는 결론에 도달했습니다.

동시 트랜잭션/세션은 동일한 레코드/인덱스 값에 대해 SELECT ... FOR UPDATE를 수행할 수 있으며 MySQL은 오류를 발생시키지 않고 즉시(비차단) 수락합니다.물론, 다른 세션이 그 작업을 수행하는 즉시, 세션 또한 레코드를 더 이상 삽입할 수 없습니다.또한 사용자나 다른 세션/트랜지스터도 상황에 대한 어떠한 정보도 얻지 못하고 실제로 기록을 삽입하려고 할 때까지 안전하게 기록을 삽입할 수 있다고 생각하지 않습니다.상황에 따라 삽입을 시도하면 교착 상태가 발생하거나 중복 키 오류가 발생합니다.

다시 말해, SELECT ... FOR UPDATE를 선택하면 다른 세션에서 해당 레코드를 삽입할 수 없지만, SELECT ... FOR UPDATE를 수행하고 해당 레코드를 찾지 못하더라도 해당 레코드를 실제로 삽입할 수 없는 가능성이 있습니다.IMHO, 이것은 "처음 쿼리 후 삽입" 방법을 쓸모없게 만듭니다.

문제의 원인은 MySQL이 존재하지 않는 레코드를 실제로 잠그는 방법을 제공하지 않기 때문입니다.두 개의 동시 세션/트랜지스터가 존재하지 않는 레코드 "FOR UPDATE"를 동시에 잠글 수 있습니다. 이는 실제로는 불가능한 일이며 개발을 훨씬 더 어렵게 만듭니다.

이를 해결할 수 있는 유일한 방법은 세마포 테이블을 사용하거나 삽입할 때 테이블 전체를 잠그는 것입니다.전체 테이블을 잠그거나 세마포어 테이블을 사용하는 방법에 대한 자세한 내용은 MySQL 문서를 참조하시기 바랍니다.

내 돈 2센트만...

가 에 지수가 있는 경우username더해야 , 이면 a 지면,를하고,이면 a)를 더해야 합니다UNIQUE1),그에를다다를ae그에n,1,SELECT * FROM user_table WHERE username = 'foo' FOR UPDATE;를 사용하면 동시 트랜잭션이 이 사용자를 생성할 수 없습니다(비반복 인덱스의 경우 "이전" 및 "다음" 가능한 값뿐만 아니라).

적합한 인덱스를 찾을 수 없는 경우 (해당 인덱스를 충족할 수 있음WHERE조건), 그러면 효율적인 기록 수집이 불가능하고 테이블 전체가 잠깁니다*.

이 잠금은 다음을 발행한 트랜잭션이 끝날 때까지 유지됩니다.SELECT ... FOR UPDATE.

이 항목에 대한 몇 가지 매우 흥미로운 정보는 이 설명서 페이지에서 찾을 수 있습니다.

* 는 효율적이라고 생각합니다. 왜냐하면 사실 레코드 잠금은 인덱스 레코드에 대한 잠금이기 때문입니다.적합한 인덱스를 찾지 못하면 기본 클러스터된 인덱스만 사용할 수 있으며, 인덱스가 완전히 잠깁니다.

존재하지 않는 레코드에 대한 잠금은 MySQL에서 작동하지 않습니다.이에 대한 몇 가지 버그 보고서가 있습니다.

한 가지 해결 방법은 새 레코드를 삽입하기 전에 기존 레코드가 잠기는 뮤텍스 테이블을 사용하는 것입니다.예를 들어 판매자와 상품 두 가지 테이블이 있습니다.판매자는 많은 상품을 가지고 있지만 중복되는 상품은 없어야 합니다.이 경우 셀러 테이블을 뮤텍스 테이블로 사용할 수 있습니다.새 제품을 삽입하기 전에 판매자의 기록에 잠금 장치가 생성됩니다.이 추가 쿼리를 사용하면 한 번에 하나의 스레드만 작업을 수행할 수 있습니다.중복 없음.교착상태는 없습니다.

당신은 "정상화" 하고 있습니까?즉, 표는 ID와 이름의 쌍 목록입니까?그리고 당신은 새로운 "이름"을 삽입하고 있습니다 (아마도 그 이름을 원하실 것입니다).id다른 테이블에 사용하기 위해)?

을 .UNIQUE(name)그리고 할

INSERT IGNORE INTO tbl (name) VALUES ($name);

그것은 어떻게 해야 하는지 설명해주지 않습니다.id방금 만들어냈지만, 당신은 그것에 대해 묻지 않았습니다.

"새로운" 것을 알아두세요.id필요 여부를 검색하기 전에 할당됩니다.따라서 이는 빠른 속도로 증가할 수 있습니다.AUTO_INCREMENT가치.

참고 항목

 INSERT ... ON DUPLICATE KEY UPDATE ...

그리고 사용할 수 있는 기술들.VALUES()그리고.LAST_INSERT_ID(id). 하지만, 질문에 진짜 목적을 언급하지 않으셨기 때문에 불필요하게 세부적인 사항으로 나누고 싶지는 않습니다.

참고: 위의 내용은 그 가치가 무엇이든 상관없습니다.autocommit또는 명시적 거래 내부에 진술이 있는지 여부.

한 번에 여러 개의 '이름'을 정규화하기 위해서는 여기에 주어진 2개의 SQL이 매우 효율적입니다. http://mysql.rjweb.org/doc.php/staging_table#normalization 그리고 이 기법은 ID를 태우는 것을 방지하고 런타임 오류를 방지합니다.

질문에 직접 답하지는 않지만, 직렬화 가능한 격리 수준을 사용하면 최종 목표를 달성할 수 있지 않을까요?이름이 중복되지 않도록 하는 것이 최종 목표라고 가정합니다.에르미타주에서:

MySQL "serializable"을 사용하면 안티-의존성 사이클(G2)을 방지할 수 있습니다.

set session transaction isolation level serializable; begin; -- T1
set session transaction isolation level serializable; begin; -- T2
select * from test where value % 3 = 0; -- T1
select * from test where value % 3 = 0; -- T2
insert into test (id, value) values(3, 30); -- T1, BLOCKS
insert into test (id, value) values(4, 42); -- T2, prints "ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction"
commit;   -- T1
rollback; -- T2

언급URL : https://stackoverflow.com/questions/17068686/how-do-i-lock-on-an-innodb-row-that-doesnt-exist-yet

반응형