행 잠금 경합 추적, 디버깅 및 수정


12

늦게, 나는 많은 행 잠금 경합에 직면했습니다. 경합 테이블은 특정 테이블 인 것 같습니다.

이것은 일반적으로 일어나는 일입니다-

  • 개발자 1은 Oracle Forms 프론트 엔드 화면에서 트랜잭션을 시작합니다
  • 개발자 2는 동일한 화면을 사용하여 다른 세션에서 다른 트랜잭션을 시작합니다.

~ 5 분 안에 프런트 엔드가 응답하지 않는 것 같습니다. 세션을 확인하면 행 잠금 경합이 표시됩니다. 모두가 던지는 "솔루션"은 세션을 죽이는 것입니다.

데이터베이스 개발자로서

  • 행 잠금 경합을 제거하기 위해 무엇을 할 수 있습니까?
  • 이 행 잠금 경합을 일으키는 저장 프로 시저의 행을 찾을 수 있습니까?
  • 이러한 코딩 문제를 줄이거 나 피하거나 제거하는 일반적인 지침은 무엇입니까?

이 질문에 너무 개방적이거나 정보가 충분하지 않다고 생각되면 언제든지 수정 / 알려주십시오. 추가 정보를 추가하기 위해 최선을 다하겠습니다.


문제가되는 테이블은 많은 삽입 및 업데이트 중이며 가장 바쁜 테이블 중 하나라고 말하고 싶습니다. SP는 매우 복잡하고 단순화하기 위해 다양한 테이블에서 데이터를 가져 와서 작업 테이블에 채우고 작업 테이블에서 많은 산술 연산이 발생하며 작업 테이블의 결과가 해당 테이블에 삽입 / 업데이트됩니다.


데이터베이스 버전은 Oracle Database 10g Enterprise Edition 릴리스 10.2.0.1.0-64 비트입니다. 논리 흐름은 두 세션에서 동일한 순서로 실행되며 트랜잭션이 너무 오랫동안 열려 있지 않거나 최소한 그렇게 생각 합니다. 그리고 트랜잭션의 활성 실행 중에 잠금이 발생합니다.


업데이트 : 테이블 행 수가 예상보다 큽니다. 약 3 백만 행입니다. 또한 세션을 추적 한 후이 테이블에 대한 몇 가지 업데이트 문이 인덱스를 사용하지 않는 것으로 나타났습니다. 왜 그럴까요-확실하지 않습니다. where 절에서 참조 된 열이 색인화됩니다. 현재 색인을 다시 작성 중입니다.


1
@Sathya-저장 프로 시저의 복잡성을 자세히 설명 할 수 있습니까? 의심되는 테이블이 엄격한 업데이트 또는 삽입 중입니까?
CoderHawk

외래 키가 여기서 역할을합니까? (때로는 인덱스가 필요함) 어떤 데이터베이스 버전이 있습니까? 논리의 흐름이 두 세션에서 동일한 순서로 실행됩니까? 거래가 오랫동안 '개방'되어 있습니까? 사용자가 시간을 생각하거나 거래를 활발히 실행하는 동안 잠금이 발생합니까?
ik_zelf

@Sandy 나는 질문을 업데이트했습니다
Sathyajith Bhat

@ik_zelf 질문을 업데이트했습니다
Sathyajith Bhat

1
이것이 왜 문제인지는 분명하지 않습니다. 오라클은 정확히해야 할 일을하고 있습니다. 이것은 단일 행에 대한 액세스를 직렬화하는 것입니다. 누군가 해당 행을 가지고 있으면 이전 버전을 읽을 수 있지만 쓰려면 잠금을 해제 할 때까지 기다려야합니다. 그것의 유일한 "수정"은 a) 멍청하지 않고 COMMIT또는 ROLLBACK적당한 시간에 또는 b) 같은 사람들이 항상 같은 행을 동시에 원하지 않도록 배열하는 것입니다.
Gaius

답변:


10

이러한 행 잠금 경합을 일으키는 저장 프로 시저의 행을 찾을 수 있습니까?

정확하지는 않지만 잠금을 유발하는 SQL 문을 가져 와서 프로 시저에서 관련 행을 식별 할 수 있습니다.

SELECT sid, sql_text
FROM v$session s
LEFT JOIN v$sql q ON q.sql_id=s.sql_id
WHERE state = 'WAITING' AND wait_class != 'Idle'
AND event = 'enq: TX - row lock contention';

이러한 코딩 문제를 줄이거 나 피하거나 피하는 일반적인 지침은 무엇입니까?

잠금에 대한 Oracle Concepts Guide 섹션에 "행은 작성자가 수정 한 경우에만 잠 깁니다."라고 말합니다. 동일한 행을 업데이트하는 다른 세션은 첫 번째 세션을 기다리 COMMIT거나 ROLLBACK계속하기 전에 대기합니다 . 문제를 제거하기 위해 사용자를 직렬화 할 수 있지만 다음은 문제가 아닌 수준으로 문제를 줄일 수있는 몇 가지 사항입니다.

  • COMMIT더 자주. 모든 COMMIT릴리스 잠금, 따라서 일괄 적으로 업데이트를 수행 할 수있는 경우 동일한 행이 필요한 다른 세션의 가능성이 줄어 듭니다.
  • 값을 변경하지 않고 행을 업데이트하지 않아야합니다. 예를 들어, UPDATE t1 SET f1=DECODE(f2,’a’,f1+1,f1);더 선택적 (더 적은 잠금 읽기)으로 다시 작성해야합니다 UPDATE t1 SET f1=f1+1 WHERE f2=’a’;. 물론 명령문을 변경해도 여전히 테이블의 대부분의 행이 잠기는 경우 변경 사항은 가독성에 도움이됩니다.
  • 가장 높은 현재 값에 테이블을 추가하기 위해 테이블을 잠그지 않고 시퀀스를 사용하고 있는지 확인하십시오.
  • 인덱스를 사용하지 않는 함수를 사용하고 있지 않은지 확인하십시오. 함수가 필요한 경우 함수 기반 인덱스로 작성하십시오.
  • 세트로 생각하십시오. 업데이트를 수행하는 PL / SQL 블록을 실행하는 루프를 단일 업데이트 문으로 다시 작성할 수 있는지 고려하십시오. 그렇지 않은 경우 대량 처리를와 함께 사용할 수 있습니다 BULK COLLECT ... FORALL.
  • 첫 번째 UPDATE와 사이에 수행되는 작업을 줄 COMMIT입니다. 예를 들어, 각 업데이트 후에 코드에서 전자 메일을 보내는 경우 전자 메일을 큐에 넣고 업데이트를 커밋 한 후 전송하는 것이 좋습니다.
  • 수행하여 핸들 대기에 응용 프로그램을 설계 SELECT ... FOR UPDATE NOWAIT또는 WAIT 2. 그런 다음 행을 잠그지 못하고 다른 세션에서 동일한 데이터를 수정하고 있음을 사용자에게 알릴 수 있습니다.

7

개발자 관점에서 답변을 드리겠습니다.

제 생각에는 설명하는 것과 같은 행 경합이 발생하면 응용 프로그램에 버그가 있기 때문입니다. 대부분의 경우 이러한 유형의 경합은 업데이트 손실 취약점의 징후입니다. AskTom 의이 스레드 는 업데이트 손실의 개념을 설명합니다.

다음과 같은 경우 손실 된 업데이트가 발생합니다.

세션 1 : Tom의 직원 레코드 읽기

세션 2 : Tom의 직원 레코드 읽기

세션 1 : Tom의 직원 레코드 업데이트

세션 2 : Tom의 직원 레코드 업데이트

세션 2는 세션 1의 변경 내용을 보지 않고 덮어 쓰기 때문에 업데이트가 손실됩니다.

업데이트 손실로 인해 심각한 부작용이 발생했습니다. 세션 1이 아직 커밋되지 않았으므로 세션 2가 차단 될 수 있습니다. 그러나 주요 문제는 세션 2가 맹목적으로 레코드를 업데이트한다는 것입니다. 두 세션이 모두 명령문을 발행한다고 가정하십시오.

UPDATE table SET col1=:col1, ..., coln=:coln WHERE id = :pk

두 명령문 모두 세션 1의 행이 수정되었음을 세션 2에 알리지 않고 session1의 수정 사항을 겹쳐 씁니다.


업데이트 손실 (및 경합 부작용)은 절대 발생하지 않아야하며 100 % 피할 수 있습니다. 낙관적 및 비관적 잠금의 두 가지 주요 방법으로이를 방지하려면 잠금을 사용해야합니다 .

1) 비관적 잠금

행을 업데이트하려고합니다. 이 모드에서는 다른 사용자가 해당 행 ( SELECT ... FOR UPDATE NOWAIT문) 에 대한 잠금을 요청하여이 행을 수정하지 못하게합니다 . 행이 이미 수정중인 경우 최종 사용자로 정상적으로 변환 할 수있는 오류 메시지가 표시됩니다 (이 행은 다른 사용자가 수정 중임). 행이 사용 가능한 경우 수정 (UPDATE) 한 후 트랜잭션이 완료 될 때마다 커미트하십시오.

2) 낙관적 잠금

행을 업데이트하려고합니다. 그러나 행을 업데이트하기 위해 여러 트랜잭션을 사용하거나 (웹 기반 상태 비 저장 응용 프로그램) 사용자가 너무 오래 잠금을 유지하지 않기를 원할 수 있습니다 (웹 기반 상태 비 저장 응용 프로그램). 다른 사람이 차단 될 수 있습니다). 이 경우 즉시 잠금을 요청하지 않습니다. 마커를 사용하여 업데이트가 발행 될 때 행이 변경되지 않았는지 확인합니다. 모든 열의 값을 캐시하거나 자동으로 업데이트되는 타임 스탬프 열 또는 시퀀스 기반 열을 사용할 수 있습니다. 무엇을 선택하든 업데이트를 수행하려고 할 때 다음과 같은 쿼리를 실행하여 해당 행의 마커가 변경되지 않았는지 확인합니다.

SELECT <...>
  FROM table
 WHERE id = :id
   AND marker = :marker
   FOR UPDATE NOWAIT

쿼리가 행을 반환하면 업데이트하십시오. 그렇지 않으면 마지막으로 쿼리 한 이후 누군가가 행을 수정했음을 의미합니다. 프로세스를 처음부터 다시 시작해야합니다.

참고 : DB에 액세스하는 모든 애플리케이션을 완전히 신뢰하는 경우 낙관적 잠금을 위해 직접 업데이트를 사용할 수 있습니다. 직접 발행 할 수 있습니다 :

UPDATE table
   SET <...>, 
       marker = marker + 1
 WHERE id = :id;

명령문이 행을 갱신하지 않으면 누군가이 행을 변경 한 것이므로 모든 것을 다시 시작해야합니다.

모든 응용 프로그램이이 체계에 동의하면 다른 사람에 의해 차단되지 않으며 맹목적인 업데이트를 피할 수 있습니다. 그러나 미리 행을 잠그지 않아도 다른 응용 프로그램, 일괄 작업 또는 직접 업데이트로 낙관적 잠금을 구현하지 않으면 무기한 잠금에 취약 할 수 있습니다. 그렇기 때문에 잠금 방식에 관계없이 항상 행을 잠그는 것이 좋습니다 (행을 잠글 때 rowid를 포함한 모든 값을 검색하므로 성능 적중은 무시할 수 있습니다).

TL; DR

  • 미리 잠그지 않고 행을 업데이트하면 응용 프로그램이 잠재적 인 "고정"에 노출됩니다. DB에 대한 모든 DML이 낙관적이거나 비관적 잠금을 구현하는 경우이를 피할 수 있습니다.
  • SELECT 문이 이전의 SELECT와 일치하는 값을 반환하는지 확인하십시오 (업데이트 손실 문제를 피하기 위해)

5

이 답변은 아마도 Daily WTF에 참가할 자격이있을 것입니다.

세션을 추적하고 검색 한 후 USER_SOURCE-근본 원인을 추적했습니다.

  • 의문의 여지없이 논리에 결함이 있었다
  • 최근에 SP에 업데이트 설명이 추가되었습니다. update 문은 기본적으로 전체 테이블을 업데이트합니다. 문제의 개발자는 필요한 문장을 업데이트하기 위해 올바른 where 절을 추가하는 것을 잊었습니다.
  • 업데이트 된 테이블은 위에서 언급 한 것처럼 가장 많이 처리 된 테이블 중 하나이며 많은 수의 레코드가있었습니다. 업데이트에는 시간이 오래 걸리고 시간이 오래 걸립니다.
  • 결과적으로 다른 세션이 테이블을 잠글 수 없었으며 행 잠금 경합에 놓이게되었습니다.
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.