2013-06-12 7 views
7

데이터베이스에 사용자 이름이 있는지 검색 한 다음 해당 사용자 이름을 SELECTINSERT 문을 가로 채지 않고 새 행으로 데이터베이스에 삽입하려면 어떻게해야합니까?아직 존재하지 않는 InnoDB 행을 잠 그려면 어떻게해야합니까?

마치 존재하지 않는 행을 잠그는 것처럼 보입니다. 존재하지 않는 행을 사용자 이름 "Foo"으로 잠 그어 데이터베이스에 존재하는지 확인하고 중단없이 데이터베이스에 삽입 할 수 있습니다.

나는 LOCK IN SHARE MODEFOR UPDATE을 사용하고 있지만, 내가 아는 한 이미 존재하는 행에서만 작동한다는 것을 알고있다. 이 상황에서 무엇을해야할지 모르겠습니다.

답변

9

AN, 다음 SELECT * FROM user_table WHERE username = 'foo' FOR UPDATE;을 실행하는 사용자를 만들 수있는 동시 transation 방지한다 (하나를 추가 pererrably A는 UNIQUE 하나 그렇지 않다면,이 경우이어야) username 인덱스 (물론 같은 "이 있으면 이전 "및 비 고유 색인의 경우"다음 "가능한 값).

인덱스가 발견되면 (WHERE 조건을 충족하기 위해) 효율적인 레코드 잠금이 불가능하며 전체 테이블이 잠길 수 있습니다 *.

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

이 주제에 대한 매우 흥미로운 정보는 these manual pages에서 찾을 수 있습니다.

*

나는 때문에 효율적인, 말을 사실 a record lock is actually a lock on index records 인치 인덱스가 적절한 인 경우 기본값 인 clustered index 만 사용할 수 있으며 완전히 잠겨집니다.

+0

색인이없는 항목에 대해 새 행을 추가하려면 어떻게해야합니까? 그것은 인덱스없이 전체 테이블을 잠그고 있습니까? – xLite

+1

예, 언급하지 않았습니다. 레코드 잠금에 적합한 인덱스가없는 경우 테이블 전체가 읽기 전용이됩니다. – RandomSeed

+0

'username' 컬럼에 'UNIQUE' 인덱스가 없으면서'user_id' 컬럼에'PRIMARY'가 있으면 어떨까요? 여전히'FOR UPDATE '로 검색하면 사용자 이름'foo '가 잠겨 있는지 확인할 수 있습니까? 내가 알아 차릴 수없는 함정이 있습니까? 예 : 'user_id'와'username' 둘 다 동시에 인덱스 ('PRIMARY'와'UNIQUE')를 가지고 있다면 잠금이 불가능하고 에러가 나거나 뭔가가 나올까요? 나는 다소 미련한 질문인데 아직도 사과하고 있습니다. 답장을 보내 주셔서 감사합니다! – xLite

7

SELECT ... FOR UPDATE가 동시 세션/트랜잭션이 동일한 레코드를 삽입하지 못하게하는 위의 대답은 사실이지만 진실은 아닙니다. 나는 현재 같은 문제로 싸우고 있으며 SELECT ... FOR UPDATE가 다음과 같은 이유로 해당 상황에서 거의 쓸모가 없다는 결론에 이르렀습니다 :

동시 트랜잭션/세션 또한 SELECT를 수행 할 수 있습니다 ... 매우 동일한 레코드/인덱스 값에 대해 FOR UPDATE를 수행하면 MySQL은 즉각적으로 (블로킹 (non-blocking)) 에러없이 던지는 것을 기꺼이 받아 들일 것입니다. 물론, 다른 세션이 그것을 마자 마자 세션은 더 이상 레코드를 삽입 할 수 없습니다. 귀하 나 다른 세션/트랜잭션은 상황에 대한 정보를 얻지 못하며 실제로 시도 할 때까지 안전하게 레코드를 삽입 할 수 있다고 생각합니다. 상황에 따라 삽입을 시도하면 교착 상태가 발생하거나 중복 키 오류가 발생합니다.

다른 말로 SELECT ... FOR UPDATE는 다른 세션이 해당 레코드를 삽입하는 것을 방지하지만 SELECT ... FOR UPDATE를 수행하고 해당 레코드를 찾을 수없는 경우에도 실제로 해당 레코드를 삽입 할 수 없습니다. IMHO는 "첫 번째 쿼리를 삽입 한 다음"메서드를 쓸모 없게 만듭니다.

문제의 원인은 MySQL이 에 실제로는 레코드를 잠글 수 없기 때문입니다. 두 개의 동시 세션/트랜잭션은 존재하지 않는 레코드 "FOR UPDATE"를 동시에 잠글 수 있습니다. 이는 실제로 가능하지 않아야하고 개발이 훨씬 더 어려워집니다.

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

그냥 내 2 센트 ...

+1

또 다른 옵션, 모든 상황에서 반드시 이상적이지는 않지만 SELECT ... FOR UPDATE를 건너 뛰고 INSERT를 수행 한 다음 결과 키 중복 오류를 처리합니다 (내 경험상 삽입이 첫 번째 작업 인 경우 훨씬 더 일관성이 있습니다. 공연). 나는 성능상의 불이익이있을 것이라고 확신하지만 많은 경우에 수행되는 다른 작업에 비해 무시할 수 있으며 뮤텍스 테이블을 생성해야하는 번거 로움을 줄일 수 있습니다. – Sanuden

+0

@Sanuden 내 응용 프로그램에서 데이터베이스에서 오류를 얻는 것은 항상 데이터베이스 또는 잘못된 (SQL) 코드에 문제가 있음을 의미하지만 * 데이터 자체에 문제가 있음을 의미하지는 않습니다. 이것은 오랜 시간 전에 심각한 이유로 건축 적 결정이었습니다. 예를 들어, 자신의 방식대로 할 때, 데이터베이스에서 돌아 오는 오류 번호를 확인해야합니다 (실제로 어떤 종류의 오류인지 판단하기 위해). 각각의 위치에서 오류가 있는지 확인하기 위해 MySQL의 추가 개발을 추적해야합니다. 오류 번호는 변하지 않으며 포팅은 어려울 것입니다. – Binarus

+0

"INSERT ... ON DUPLICATE KEY UPDATE"는 지속적으로'X' 잠금을 생성합니다. 이것은 두 프로세스가 트랜잭션에 들어가는 것을 막는 데 사용할 수 있습니다. https://stackoverflow.com/a/48453879/710358 – M1L0U

4

MySQL에서 존재하지 않는 레코드 잠금은 작동하지 않습니다.

한 가지 해결 방법은 새 레코드를 삽입하기 전에 기존 레코드가 잠겨됩니다 mutex table을 사용하는 것입니다 그것에 대해 몇 가지 버그보고가있다. 예를 들어 판매자와 제품의 두 테이블이 있습니다. 판매자에게는 많은 제품이 있지만 중복 제품이 없어야합니다. 이 경우 판매자 테이블은 뮤텍스 테이블로 사용할 수 있습니다. 신제품이 삽입되기 전에 판매자 레코드에 잠금 장치가 작성됩니다. 이 추가 조회를 사용하면 주어진 시간에 하나의 스레드 만 조치를 수행 할 수 있습니다. 중복이 없습니다. 교착 상태가 발생하지 않습니다.