InnoDB 행 잠금-구현 방법


13

나는 지금 주위를 둘러보고 mysql 사이트를 읽었으며 여전히 그것이 어떻게 작동하는지 정확히 볼 수는 없다.

쓰기 결과를 선택하고 행 잠금으로 변경하고 변경 내용을 작성하고 잠금을 해제하고 싶습니다. audocommit이 켜져 있습니다.

계획

id (int)
name (varchar50)
status (enum 'pending', 'working', 'complete')
created (datetime)
updated (datetime) 

보류 상태 인 항목을 선택하고 작동하도록 업데이트하십시오. 독점적 인 쓰기를 사용하여 동일한 항목이 두 번 픽업되지 않도록하십시오.

그래서;

"SELECT id FROM `items` WHERE `status`='pending' LIMIT 1 FOR WRITE"

결과에서 ID를 얻습니다

"UPDATE `items` SET `status`='working', `updated`=NOW() WHERE `id`=<selected id>

자물쇠를 풀기 위해 무엇을해야합니까, 위에서했던 것처럼 작동합니까?

답변:


26

원하는 것은 트랜잭션 컨텍스트 내에서 SELECT ... FOR UPDATE 입니다. UPDATE를 실행하는 것처럼 SELECT FOR UPDATE는 선택된 행에 단독 잠금을 설정합니다. 또한 격리 수준이 명시 적으로 설정된 것과 상관없이 READ COMMITTED 격리 수준에서 암시 적으로 실행됩니다. SELECT ... FOR UPDATE는 동시성에 매우 좋지 않으므로 꼭 필요한 경우에만 사용해야합니다. 또한 사람들이 잘라서 붙여 넣을 때 코드베이스가 증가하는 경향이 있습니다.

다음은 FOR UPDATE 쿼리의 일부 동작을 보여주는 Sakila 데이터베이스의 세션 예입니다.

먼저, 명확하게하기 위해 트랜잭션 격리 수준을 REPEATABLE READ로 설정합니다. InnoDB의 기본 격리 수준이므로 일반적으로 필요하지 않습니다.

session1> SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
session1> BEGIN;
session1> SELECT first_name, last_name FROM customer WHERE customer_id = 3;
+------------+-----------+
| first_name | last_name |
+------------+-----------+
| LINDA      | WILLIAMS  |
+------------+-----------+
1 row in set (0.00 sec)    

다른 세션에서이 행을 업데이트하십시오. Linda는 결혼하여 이름을 변경했습니다.

session2> UPDATE customer SET last_name = 'BROWN' WHERE customer_id = 3;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

session1로 돌아가서, 우리가 반복해서 읽을 수 있었기 때문에 Linda는 여전히 LINDA WILLIAMS입니다.

session1> SELECT first_name, last_name FROM customer WHERE customer_id = 3;
+------------+-----------+
| first_name | last_name |
+------------+-----------+
| LINDA      | WILLIAMS  |
+------------+-----------+
1 row in set (0.00 sec)

그러나 이제이 행에 대한 독점 액세스 권한을 원하므로 행에서 FOR UPDATE를 호출합니다. 이제이 트랜잭션 외부의 session2에서 업데이트 된 최신 버전의 행이 다시 나타납니다. 반복 가능한 읽기가 아니며 읽기가 완결되었습니다.

session1> SELECT first_name, last_name FROM customer WHERE customer_id = 3 FOR UPDATE;
+------------+-----------+
| first_name | last_name |
+------------+-----------+
| LINDA      | BROWN     |
+------------+-----------+
1 row in set (0.00 sec)

session1에 설정된 잠금을 테스트 해 봅시다. session2는 행을 업데이트 할 수 없습니다.

session2> UPDATE customer SET last_name = 'SMITH' WHERE customer_id = 3;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

그러나 우리는 여전히 그것에서 선택할 수 있습니다

session2> SELECT c.customer_id, c.first_name, c.last_name, a.address_id, a.address FROM customer c JOIN address a USING (address_id) WHERE c.customer_id = 3;
+-------------+------------+-----------+------------+-------------------+
| customer_id | first_name | last_name | address_id | address           |
+-------------+------------+-----------+------------+-------------------+
|           3 | LINDA      | BROWN     |          7 | 692 Joliet Street |
+-------------+------------+-----------+------------+-------------------+
1 row in set (0.00 sec)

그리고 외래 키 관계로 자식 테이블을 업데이트 할 수 있습니다

session2> UPDATE address SET address = '5 Main Street' WHERE address_id = 7;
Query OK, 1 row affected (0.05 sec)
Rows matched: 1  Changed: 1  Warnings: 0

session1> COMMIT;

또 다른 부작용은 교착 상태를 일으킬 가능성이 크게 증가한다는 것입니다.

특정한 경우에는 다음을 원할 것입니다.

BEGIN;
SELECT id FROM `items` WHERE `status`='pending' LIMIT 1 FOR UPDATE;
-- do some other stuff
UPDATE `items` SET `status`='working', `updated`=NOW() WHERE `id`=<selected id>;
COMMIT;

"다른 작업을 수행하십시오"가 불필요하고 실제로 행에 대한 정보를 유지할 필요가없는 경우, SELECT FOR UPDATE는 불필요하고 낭비이며 대신 업데이트를 실행할 수 있습니다.

UPDATE `items` SET `status`='working', `updated`=NOW() WHERE `status`='pending' LIMIT 1;

이것이 의미가 있기를 바랍니다.


3
감사. 두 개의 스레드가 "SELECT id FROM itemsWHERE status= 'pending'LIMIT 1 FOR UPDATE;" 와 함께 나올 때 내 문제를 해결하지 못하는 것 같습니다 . 둘 다 같은 줄을 본 후 하나는 다른 하나를 잠급니다. 나는 어떻게 든 그것이 잠긴 행을 우회하고 보류중인 다음 항목으로 갈 수 있기를 바라고있었습니다.
Wizzard

1
데이터베이스의 특성상 일관성있는 데이터를 반환한다는 것입니다. 값이 업데이트되기 전에 해당 쿼리를 두 번 실행하면 동일한 결과가 다시 나타납니다. 내가 알고있는 "행이 잠겨 있지 않으면이 쿼리와 일치하는 첫 번째 값을 가져 오십시오"SQL 확장이 없습니다. 관계형 데이터베이스 위에 큐를 구현하는 것처럼 보입니다. 그 경우입니까?
Aaron Brown

아론; 그래, 내가하려고하는거야. 나는 기어 맨과 같은 것을 사용하는 것을 보았습니다. 그러나 그것은 흉상이었습니다. 다른 생각이 있으십니까?
Wizzard

나는 이것을 읽어야한다고 생각한다 : engineyard.com/blog/2011/…- 메시지 대기열의 경우, 선택한 클라이언트 언어에 따라 많은 것들이 있습니다. ActiveMQ, Resque (Ruby + Redis), ZeroMQ, RabbitMQ 등
Aaron Brown

세션 1의 업데이트가 커밋 될 때까지 세션 2가 읽기를 차단하도록하려면 어떻게해야합니까?
CMCDragonkai

2

InnoDB 스토리지 엔진을 사용하는 경우 행 수준 잠금을 사용합니다. 멀티 버전과 함께 사용하면 주어진 테이블을 여러 클라이언트가 동시에 읽고 수정할 수 있으므로 쿼리 동시성이 향상됩니다. 행 수준 동시성 속성은 다음과 같습니다.

다른 클라이언트가 동일한 행을 동시에 읽을 수 있습니다.

클라이언트마다 다른 행을 동시에 수정할 수 있습니다.

다른 클라이언트가 동시에 같은 행을 수정할 수 없습니다. 한 트랜잭션이 행을 수정하면 첫 번째 트랜잭션이 완료 될 때까지 다른 트랜잭션이 동일한 행을 수정할 수 없습니다. 커밋되지 않은 읽기 격리 수준을 사용하지 않으면 다른 트랜잭션도 수정 된 행을 읽을 수 없습니다. 즉, 수정되지 않은 원래 행을 보게됩니다.

기본적으로 명시 적 잠금을 지정할 필요는 없습니다. 어떤 상황에서는 명시 적 잠금에 대한 명시 적 잠금 세부 사항을 제공해야 할 수도 있지만 InnoDB는 iteslf를 처리합니다.

다음 목록은 사용 가능한 잠금 유형과 그 효과를 설명합니다.

읽다

읽을 테이블을 잠급니다. READ 잠금은 테이블에서 데이터를 검색하는 SELECT와 같은 읽기 쿼리를 위해 테이블을 잠급니다. 잠금을 보유한 클라이언트에서도 테이블을 수정하는 INSERT, DELETE 또는 UPDATE와 같은 쓰기 조작을 허용하지 않습니다. 테이블이 읽기 위해 잠겨 있으면 다른 클라이언트가 동시에 테이블에서 읽을 수 있지만 클라이언트는 테이블에 쓸 수 없습니다. 읽기 잠금 상태 인 테이블에 쓰려는 클라이언트는 현재 테이블을 읽는 모든 클라이언트가 잠금을 해제하고 해제 할 때까지 기다려야합니다.

쓰다

쓰기 위해 테이블을 잠급니다. WRITE 잠금은 독점 잠금입니다. 테이블을 사용하지 않을 때만 얻을 수 있습니다. 일단 확보 잠금을 보유한 클라이언트 만이 테이블에서 읽거나 테이블에 쓸 수 있습니다. 다른 클라이언트는 읽거나 쓸 수 없습니다. 다른 클라이언트는 읽기 또는 쓰기를 위해 테이블을 잠글 수 없습니다.

현지 읽기

읽기 위해 테이블을 잠그지 만 동시 삽입을 허용합니다. 동시 삽입은 "리더 블록 작성자"원칙에 대한 예외입니다. MyISAM 테이블에만 적용됩니다. 삭제 또는 업데이트 된 레코드로 인해 MyISAM 테이블에 중간에 구멍이없는 경우 삽입은 항상 테이블 끝에서 발생합니다. 이 경우 테이블에서 읽는 클라이언트는 READ LOCAL 잠금으로 테이블을 잠 가서 읽기 잠금을 보유한 클라이언트가 테이블을 읽는 동안 다른 클라이언트가 테이블에 삽입 할 수 있습니다. MyISAM 테이블에 구멍이있는 경우 OPTIMIZE TABLE을 사용하여 테이블 조각 모음을 수행하여 구멍을 제거 할 수 있습니다.


답변 해주셔서 감사합니다. 이 테이블과 100 개의 클라이언트가 보류중인 항목을 검사함에 따라 2-3 개의 클라이언트가 동일한 보류중인 행을 얻습니다. 테이블 잠금이 느려집니다.
Wizzard

0

다른 대안은 마지막으로 성공한 잠금 시간을 저장 한 열을 추가하는 것입니다. 그런 다음 행을 잠 그려는 다른 항목은 삭제되거나 5 분 (또는 무엇이든)이 경과 할 때까지 기다려야합니다.

뭔가 ...

Schema

id (int)
name (varchar50)
status (enum 'pending', 'working', 'complete')
created (datetime)
updated (datetime)
lastlock (int)

lastlock은 유닉스 타임 스탬프를 비교하기 더 쉽고 (아마도 더 빠름) 저장하기 때문에 정수입니다.

// 시맨틱 스를 실례합니다.

UPDATE items 
  SET lastlock = UNIX_TIMESTAMP() 
WHERE 
  lastlock = 0
  OR (UNIX_TIMESTAMP() - lastlock) > 360;

그런 다음 한 번에 두 프로세스로 행을 업데이트 할 수 없기 때문에 업데이트 된 행 수를 확인하십시오. 행을 업데이트하면 잠금 상태가됩니다. PHP를 사용한다고 가정하면 mysql_affected_rows ()를 사용하고 그 리턴 값이 1이면 성공적으로 잠근 것입니다.

그런 다음 필요한 작업을 완료 한 후 lastlock을 0으로 업데이트하거나 게으르고 다음 잠금 시도가 성공할 때 5 분 동안 기다릴 수 있습니다.

편집 : 시계가 한 시간 뒤로 이동하여 수표가 무효화 될 수 있으므로 서머 타임 변경이 예상대로 작동하는지 확인하려면 약간의 작업이 필요할 수 있습니다. 유닉스 타임 스탬프가 UTC로되어 있는지 확인해야합니다.


-1

또는 레코드 필드를 조각화하여 병렬 쓰기를 허용하고 행 잠금을 무시할 수 있습니다 (조각화 된 json 쌍 스타일). 따라서 복합 읽기 레코드의 한 필드가 정수 / 실제이면 해당 필드의 조각 1-8을 가질 수 있습니다 (8 개의 쓰기 레코드 / 행). 그런 다음 별도의 읽기 조회에 쓸 때마다 조각 라운드 로빈을 합산하십시오. 이를 통해 최대 8 명의 동시 사용자를 동시에 사용할 수 있습니다.

부분 총계를 생성하는 각 조각으로 만 작업하므로 충돌 및 실제 병렬 업데이트가 없습니다 (예 : 전체 통합 읽기 레코드가 아닌 각 조각을 쓰기 잠금). 이것은 분명히 숫자 필드에서만 작동합니다. 결과를 저장하기 위해 수학적 수정에 의존하는 것.

따라서 통합 읽기 레코드 당 통합 읽기 필드 당 여러 쓰기 조각입니다. 이러한 숫자 조각은 ECC, 암호화 및 블록 수준 전송 / 저장에도 적합합니다. 쓰기 조각이 많을수록 포화 된 데이터에 대한 병렬 / 동시 쓰기 액세스 속도가 빨라집니다.

MMORPG는 많은 수의 플레이어가 Area of ​​Effect 기술로 서로를 때리기 시작했을 때이 문제로 인해 큰 어려움을 겪고 있습니다. 이러한 여러 플레이어는 모두 동시에 동시에 모든 플레이어를 작성 / 업데이트해야하므로 통합 플레이어 레코드에 쓰기 행 잠금 폭풍이 발생합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.