두 명 이상의 사용자가 동일한 데이터베이스 항목을 동시에 수정하지 못하도록 보호하는 방법이 있습니까?
두 번째 커밋 / 저장 작업을 수행하는 사용자에게 오류 메시지를 표시하는 것은 허용되지만 데이터를 자동으로 덮어 쓰면 안됩니다.
사용자가 “뒤로”버튼을 사용하거나 단순히 브라우저를 닫고 잠금을 영원히 남겨 둘 수 있으므로 항목을 잠그는 것은 옵션이 아니라고 생각합니다.
답변
이것은 Django에서 낙관적 잠금을 수행하는 방법입니다.
updated = Entry.objects.filter(Q(id=e.id) && Q(version=e.version))\
.update(updated_field=new_value, version=e.version+1)
if not updated:
raise ConcurrentModificationException()
위에 나열된 코드는 Custom Manager 에서 메서드로 구현할 수 있습니다 .
다음과 같은 가정을하고 있습니다.
- filter (). update ()는 필터가 게으 르기 때문에 단일 데이터베이스 쿼리를 생성합니다.
- 데이터베이스 쿼리는 원자 적입니다.
이러한 가정은 다른 사람이 이전에 항목을 업데이트하지 않았 음을 확인하기에 충분합니다. 이 방법으로 여러 행이 업데이트되면 트랜잭션을 사용해야합니다.
경고 Django Doc :
update () 메서드는 SQL 문으로 직접 변환됩니다. 직접 업데이트를위한 대량 작업입니다. 모델에서 save () 메서드를 실행하지 않거나 pre_save 또는 post_save 신호를 방출하지 않습니다.
답변
이 질문은 약간 오래되었고 내 대답은 조금 늦었지만 내가 이해 한 후에 는 Django 1.4에서 다음을 사용하여 수정되었습니다 .
select_for_update(nowait=True)
참조 문서를
트랜잭션이 끝날 때까지 행을 잠그고 지원되는 데이터베이스에서 SELECT … FOR UPDATE SQL 문을 생성하는 쿼리 세트를 반환합니다.
일반적으로 다른 트랜잭션이 선택한 행 중 하나에 대해 이미 잠금을 획득 한 경우 잠금이 해제 될 때까지 쿼리가 차단됩니다. 이것이 원하는 동작이 아닌 경우 select_for_update (nowait = True)를 호출합니다. 이렇게하면 통화가 차단되지 않습니다. 충돌하는 잠금이 이미 다른 트랜잭션에 의해 획득 된 경우 쿼리 세트가 평가 될 때 DatabaseError가 발생합니다.
물론 이것은 백엔드가 “업데이트를 위해 선택”기능을 지원하는 경우에만 작동합니다. 예를 들어 sqlite는 지원하지 않습니다. 불행히도 : nowait=True
는 MySql에서 지원되지 않습니다. nowait=False
여기서는 잠금이 해제 될 때까지만 차단됩니다 :를 사용해야 합니다.
답변
실제로 트랜잭션은 여러 HTTP 요청을 통해 실행되는 트랜잭션을 원하지 않는 한 여기에서별로 도움이되지 않습니다.
이러한 경우에 일반적으로 사용하는 것은 “낙관적 잠금”입니다. Django ORM은 내가 아는 한 지원하지 않습니다. 그러나이 기능을 추가하는 것에 대해 몇 가지 논의가있었습니다.
그래서 당신은 당신 자신입니다. 기본적으로해야 할 일은 모델에 “version”필드를 추가하고이를 숨겨진 필드로 사용자에게 전달하는 것입니다. 일반적인 업데이트주기는 다음과 같습니다.
- 데이터를 읽고 사용자에게 보여줍니다.
- 사용자 수정 데이터
- 사용자가 데이터를 게시
- 앱은 데이터베이스에 다시 저장합니다.
낙관적 잠금을 구현하려면 데이터를 저장할 때 사용자로부터받은 버전이 데이터베이스의 버전과 동일한 지 확인한 다음 데이터베이스를 업데이트하고 버전을 증가시킵니다. 그렇지 않은 경우 데이터가로드 된 이후 변경 사항이 있음을 의미합니다.
다음과 같은 단일 SQL 호출로이를 수행 할 수 있습니다.
UPDATE ... WHERE version = 'version_from_user';
이 호출은 버전이 여전히 동일한 경우에만 데이터베이스를 업데이트합니다.
답변
Django 1.11에는 비즈니스 로직 요구 사항에 따라이 상황을 처리 할 수있는 세 가지 편리한 옵션 이 있습니다.
Something.objects.select_for_update()
모델이 해제 될 때까지 차단됩니다.Something.objects.select_for_update(nowait=True)
및 캐치DatabaseError
모델은 현재 업데이트에 잠겨있는 경우Something.objects.select_for_update(skip_locked=True)
현재 잠겨있는 개체를 반환하지 않습니다.
다양한 모델에 대한 대화 형 워크 플로와 배치 워크 플로가 모두있는 애플리케이션에서 동시 처리 시나리오 대부분을 해결하는 데이 세 가지 옵션을 찾았습니다.
“대기” select_for_update
는 순차 배치 프로세스에서 매우 편리합니다. 모두 실행하기를 원하지만 시간을 들여야합니다. 는 nowait
사용자가 현재 업데이트 잠겨 객체를 수정하고자 할 때 사용됩니다 – 난 그냥이 순간에 수정되는 것을 말할 것이다.
이것은 skip_locked
사용자가 개체의 재검색을 트리거 할 수있는 다른 유형의 업데이트에 유용하며, 트리거되는 한 누가 트리거하는지 상관하지 않으므로 skip_locked
중복 된 트리거를 자동으로 건너 뛸 수 있습니다.
답변
향후 참조를 위해 https://github.com/RobCombs/django-locking을 확인 하십시오 . 사용자가 페이지를 떠날 때 자바 스크립트 잠금 해제와 잠금 시간 초과 (예 : 사용자의 브라우저가 충돌하는 경우)를 혼합하여 영구적 인 잠금을 남기지 않는 방식으로 잠금을 수행합니다. 문서는 꽤 완전합니다.
답변
이 문제에 관계없이 적어도 django 트랜잭션 미들웨어를 사용해야합니다.
여러 사용자가 동일한 데이터를 편집하는 실제 문제에 관해서는 … 예, 잠금을 사용하십시오. 또는:
사용자가 업데이트하는 버전을 확인하고 (안전하게 수행하여 사용자가 최신 사본을 업데이트한다고 말하기 위해 시스템을 해킹 할 수 없도록하십시오!) 해당 버전이 최신 버전 인 경우에만 업데이트하십시오. 그렇지 않으면 사용자에게 편집 중이던 원본 버전, 제출 된 버전 및 다른 사람이 작성한 새 버전이 포함 된 새 페이지를 다시 보냅니다. 변경 사항을 완전히 최신 버전으로 병합하도록 요청하십시오. diff + patch와 같은 도구 세트를 사용하여 자동 병합을 시도 할 수 있지만 어쨌든 실패 사례에 대해 작동하는 수동 병합 방법이 필요하므로 먼저 시작하십시오. 또한 누군가가 의도하지 않거나 의도적으로 병합을 엉망으로 만들 경우 버전 기록을 보존하고 관리자가 변경 사항을 되돌릴 수 있도록 허용해야합니다. 그러나 어쨌든 당신은 그것을 가져야 할 것입니다.
대부분의 작업을 수행하는 django 앱 / 라이브러리가있을 가능성이 높습니다.
답변
찾아야 할 또 다른 것은 “원자”라는 단어입니다. 원자 적 작업은 데이터베이스 변경이 성공적으로 발생하거나 분명히 실패 함을 의미합니다. 빠른 검색은 Django의 원자 연산에 대해 묻는 이 질문을 보여줍니다 .
