DB에 무언가가 있는지 확인하고 빠르게 실패하거나 DB 예외를 기다려야합니까?


32

두 가지 수업이 있습니다.

public class Parent 
{
    public int Id { get; set; }
    public int ChildId { get; set; }
}

public class Child { ... }

에 할당 ChildId할 때 ParentDB에 존재하는지 먼저 확인하거나 DB가 예외를 throw 할 때까지 기다려야합니까?

예를 들어 (Entity Framework Core 사용) :

검사의 이러한 종류는 온통의 인터넷 에도 공식 마이크로 소프트의 문서에 : https://docs.microsoft.com/en-us/aspnet/mvc/overview/getting-started/getting-started-with-ef-using- mvc / 처리 동시성-엔티티 프레임 워크-ASP-net-mvc-application # modify-부서 컨트롤러- 추가 예외 처리가 있습니다.SaveChanges

또한이 확인의 주요 목적은 API 사용자에게 친숙한 메시지와 알려진 HTTP 상태를 반환하고 데이터베이스 예외를 완전히 무시하지 않는 것입니다. 그리고 발생 될 수있는 유일한 장소 예외는 내부에 SaveChanges또는 SaveChangesAsync전화는 전화 할 때 너무 예외가되지 않습니다 ... FindAsyncAny. 따라서 자식이 존재하지만 이전에 삭제 된 SaveChangesAsync경우 동시성 예외가 발생합니다.

foreign key violation"ID가 {Child.ChildId} 인 자식을 찾을 수 없습니다."를 표시하기 위해 예외를 형식화하는 것이 훨씬 더 어렵다는 사실 때문에이를 수행했습니다 .

public async Task<ActionResult<Parent>> CreateParent(Parent parent)
{
    // is this code redundant?
   // NOTE: its probably better to use Any isntead of FindAsync because FindAsync selects *, and Any selects 1
    var child = await _db.Children.FindAsync(parent.ChildId);
    if (child == null)
       return NotFound($"Child with id {parent.ChildId} could not be found.");

    _db.Parents.Add(parent);    
    await _db.SaveChangesAsync();        

    return parent;
}

대:

public async Task<ActionResult<Parent>> CreateParent(Parent parent)
{
    _db.Parents.Add(parent);
    await _db.SaveChangesAsync();  // handle exception somewhere globally when child with the specified id doesn't exist...  

    return parent;
}

Postgres의 두 번째 예는 https://www.postgresql.org/docs/9.4/static/errcodes-appendix.html23503 foreign_key_violation 오류를 발생시킵니다.

EF와 같은 ORM에서 예외를 처리하는 단점은 특정 데이터베이스 백엔드에서만 작동한다는 것입니다. SQL 서버 또는 다른 것으로 전환하려는 경우 오류 코드가 변경되므로 더 이상 작동하지 않습니다.

최종 사용자를 위해 예외의 형식을 올바르게 지정하지 않으면 개발자 이외의 다른 사람이 원하지 않는 내용이 노출 될 수 있습니다.

관련 :

https://stackoverflow.com/questions/6171588/preventing-race-condition-of-if-exists-update-else-insert-in-entity-framework

https://stackoverflow.com/questions/4189954/implementing-if-not-exists-insert-using-entity-framework-without-race-conditions

https://stackoverflow.com/questions/308905/should-there-be-a-transaction-for-read-queries


2
귀하의 연구를 공유하면 모든 사람을 도울 수 있습니다. 당신이 무엇을 시도했고 왜 그것이 당신의 요구를 충족시키지 못했는지 알려주십시오. 이것은 당신이 시간을내어 자신을 돕기 위해 노력했고, 명백한 답변을 되풀이하는 것을 막아 주며, 무엇보다도보다 구체적이고 적절한 답변을 얻는 데 도움이됩니다. 또한 물어
gnat

5
다른 사람들이 언급했듯이 NotFound 확인과 동시에 레코드를 삽입하거나 삭제할 수 있습니다. 이런 이유로 먼저 확인하는 것은 용납 할 수없는 해결책 인 것 같습니다. 당신은 다른 데이터베이스 백엔드에 이식 할 수 없습니다 포스트 그레스 고유의 예외 처리를 쓰기에 대해 우려하는 경우, 핵심 기능은 데이터베이스 특정 클래스 (SQL, 포스트 그레스 등)로 확장 할 수있는 방법으로 예외 핸들러를 구성하려고
billrichards을

3
코멘트를 통해 찾고, 나는이 말을해야합니다 상투적에서 정지 생각 . "빠른 실패"는 맹목적으로 따라야하거나 따라야하는 문맥 상 규칙이 아닌 격리 된 것이 아닙니다. 경험상 규칙입니다. 실제로 달성하려는 목표를 항상 분석 한 다음 목표 달성에 도움이되는지 여부를 고려하여 기술을 고려하십시오. "실패"는 의도하지 않은 부작용을 방지하는 데 도움이됩니다. 또한 "빠른 실패"란 실제로 "문제가 있음을 감지하자마자 실패"한다는 의미입니다. 문제가 감지 되 자마자 기술이 모두 실패하므로 다른 고려 사항을 살펴 봐야합니다.
jpmc26

1
@ Konrad 예외는 무엇과 관련이 있습니까? 경쟁 조건을 코드에있는 것으로 생각하지 마십시오. 우주의 속성입니다. 아무것도, 아무것도 번 이상 완전히 제어하지 않는 자원 (예 : 직접 메모리 액세스, 공유 메모리, 데이터베이스, REST API를, 파일 시스템, 등 등)에 접촉하고 변화가있을 것으로 기대 잠재적 인 경쟁 조건이 있습니다. 우리 예외 가없는 C에서 이것을 처리 합니다. 최소한 하나의 브랜치가 해당 리소스의 상태를 망칠 경우 제어하지 않는 리소스의 상태를 분기하지 마십시오.
Jared Smith

1
@DanielPryden 내 질문에, 데이터베이스 예외를 처리하고 싶지 않다고 말하지 않았습니다 (예외가 불가피하다는 것을 알고 있습니다). 많은 사람들이 오해했다고 생각합니다. 저는 최종 사용자가 읽을 수있는 웹 API에 대한 친절한 오류 메시지를 원했습니다 Child with id {parent.ChildId} could not be found.. 그리고 "외국 키 위반"의 형식을 지정하면이 경우에 더 나빠집니다.
Konrad

답변:


3

혼란스러운 질문이 아니라 , 먼저 DB 예외를 처리하는 것이 아니라 확인해야합니다.

우선, 귀하의 예에서는 데이터베이스에서 직접 EF를 사용하여 SQL을 실행하는 데이터 계층에 있습니다. 당신은 코드를 실행하는 것과 같습니다

select * from children where id = x
//if no results, perform logic
insert into parents (blah)

제안하는 대안은 다음과 같습니다.

insert into parents (blah)
//if exception, perform logic

조건 논리를 실행하기 위해 예외를 사용하는 것은 느리고 보편적으로 눈살을 찌푸립니다.

경쟁 조건이 있으므로 거래를 사용해야합니다. 그러나 이것은 코드에서 완전히 수행 할 수 있습니다.

using (var transaction = new TransactionScope())
{
    var child = await _db.Children.FindAsync(parent.ChildId);
    if (child == null) 
    {
       return NotFound($"Child with id {parent.ChildId} could not be found.");
    }

    _db.Parents.Add(parent);    
    await _db.SaveChangesAsync();        
    transaction.Complete();

    return parent;
}

중요한 것은 자신에게 묻는 것입니다.

"이 상황이 예상됩니까?"

그렇지 않다면, 확실하게 삽입하고 예외를 던져라. 그러나 발생할 수있는 다른 오류처럼 예외를 처리하십시오.

그것이 발생할 것으로 예상되는 경우, 예외가 아니며, 자녀가 먼저 존재하는지 확인하고, 그렇지 않은 경우 적절한 친절한 메시지로 응답해야합니다.

편집 -이것에 대해 많은 논란이 있습니다. 공감하기 전에 다음 사항을 고려하십시오.

A. FK 제약 조건이 두 개인 경우 어떻게해야합니까? 누락 된 오브젝트를 해결하기 위해 예외 메시지 구문 분석을 옹호 하시겠습니까?

B. 누락 된 경우 하나의 SQL 문만 실행됩니다. 두 번째 쿼리의 추가 비용이 발생하는 조회수입니다.

C. 보통 이드 그것은 당신이 하나를 알고있는 상황을 상상하기 어렵다 대리의 열쇠가 될 것입니다 그리고 당신은 확신는 DB에의하지 않습니다. 확인하는 것이 이상합니다. 그러나 사용자가 입력 한 자연 키가 무엇입니까? 그것은 존재하지 않을 가능성이 높습니다


의견은 긴 토론을위한 것이 아닙니다. 이 대화는 채팅 으로 이동 되었습니다 .
maple_shaft

1
이것은 완전히 잘못되어 오도됩니다! 내가 항상 싸워야하는 나쁜 전문가를 만드는 것은 이와 같은 대답입니다. SELECT는 테이블을 잠그지 않으므로 SELECT와 INSERT, UPDATE 또는 DELTE 사이에서 레코드가 변경 될 수 있습니다. 따라서 소프트웨어가 불충분하고 생산 과정에서 사고가 발생할 수 있습니다.
다니엘 로보

1
@DanielLobo transactionscope로 수정
Ewan

1
당신이 나를 믿지 않으면 그것을 테스트
Ewan

1
@yusha 여기에 코드가 있습니다
Ewan

111

독창성을 확인한 다음 설정하는 것은 반 패턴입니다. 확인 시간과 쓰기 시간 사이에 ID가 동시에 삽입되는 경우가 항상 있습니다. 데이터베이스는 제약 및 트랜잭션과 같은 메커니즘을 통해이 문제를 처리 할 수 ​​있습니다. 대부분의 프로그래밍 언어는 그렇지 않습니다. 따라서 데이터 일관성을 중요하게 생각하는 경우 데이터를 전문가 (데이터베이스)에게 맡기십시오. 즉, 삽입을 수행하고 예외가 발생하면 예외를 포착하십시오.


34
점검과 실패는 단순히 "시도"하는 것보다 빠르지 않으며 최선을 다하기를 바랍니다. 이전에는 시스템에서 2 개의 작업을 구현하고 DB에서 2 개의 작업을 수행하고, 최신 작업 중 하나만 수행함을 의미합니다. 점검이 DB 서버에 위임되었습니다. 또한 네트워크에 대한 하나의 적은 홉과 DB가 수행해야 할 하나의 적은 작업을 의미합니다. 우리는 DB에 대한 하나 이상의 쿼리가 저렴하다고 생각할 수도 있지만, 크게 생각하는 것을 종종 잊어 버립니다. 높은 동시성으로 인해 쿼리가 수백 번 이상 트리거됩니다. 전체 트래픽을 DB로 버릴 수 있습니다. 그 문제가 당신의 결정에 달려 있습니다.
Laiv

6
@Konrad 내 입장은 기본적으로 올바른 선택은 스스로 실패하는 하나의 쿼리이며, 자체적으로 정당화 할 증거 가있는 별도의 쿼리 사전 비행 접근법입니다. "문제가되었습니다": 트랜잭션 사용하고 있거나 ToCToU 오류로부터 안전하다는 것을 보장하고 있습니까? 당신이 게시 한 코드에서 나에게 분명하지 않지만, 그렇지 않은 경우 실제로 폭탄이 터지기 전에 똑딱 거리는 폭탄이 문제가되는 방식으로 이미 문제가되었습니다.
mtraceur

4
@Konrad EF Core는 암시 적으로 수표와 삽입을 하나의 트랜잭션에 넣지 않으므로 명시 적으로 요청해야합니다. 트랜잭션이 없으면 데이터베이스 상태가 검사와 삽입간에 변경 될 수 있으므로 먼저 검사는 의미가 없습니다. 트랜잭션이 있어도 데이터베이스가 발밑에서 변경되는 것을 막을 수 없습니다. 몇 년 전에 Oracle에서 EF를 사용하여 문제가 발생했습니다. 여기서는 db가 지원하지만 Entity가 트랜잭션 내에서 읽기 레코드의 잠금을 트리거하지 않았으며 삽입 만 트랜잭션으로 처리되었습니다.
Mr.Mindor

3
"독 특성을 확인한 다음 설정하는 것은 반 패턴입니다"라고 말하지 않을 것입니다. 그것은 다른 수정이 일어나지 않는다고 가정 할 수 있는지 여부와 검사가 존재하지 않을 때 검사가 더 유용한 결과 (실제로 독자에게 무언가를 의미하는 오류 메시지조차도)를 생성하는지 여부에 달려 있습니다. 동시 웹 요청을 처리하는 데이터베이스를 사용하면 아니요, 다른 수정이 발생하지 않을 것이라고 보장 할 수는 없지만 합리적인 가정 일 경우가 있습니다.
jpmc26

5
고유성을 먼저 확인한다고해서 가능한 장애를 처리 할 필요가 없습니다. 반면에, 행동은 그들 중 하나가 종종 더 할 수있는 작업을 수행보다 시작하기 전에 성공하기 위해 모든 가능성 여부를 확인, 여러 작업을 수행 할 필요하면 가능성이 다시 압연 될 필요가있다. 초기 점검을 수행하면 롤백이 필요한 모든 상황을 피할 수는 없지만 그러한 경우의 빈도를 줄이는 데 도움이 될 수 있습니다.
supercat

38

나는 당신이“실패”라고 부르는 것과 내가 부르는 것은 같지 않다고 생각합니다.

데이터베이스에 변경을 요청하고 실패를 처리하도록 지시 하는 것은 빠릅니다. 당신의 길은 복잡하고 느리고 특히 신뢰할 수 없습니다.

당신의 기술은 빨리 실패하지 않으며,“미리 비행”입니다. 때로는 좋은 이유가 있지만 데이터베이스를 사용할 때는 그렇지 않습니다.


1
한 클래스가 다른 클래스에 종속 될 때 두 번째 쿼리가 필요한 경우가 있으므로 그러한 경우에는 선택의 여지가 없습니다.
Konrad

4
그러나 여기서는 아닙니다. 그리고 데이터베이스 쿼리는 매우 영리 할 수 ​​있으므로 일반적으로“선택하지 않음”을 의심합니다.
gnasher729

1
나는 또한 응용 프로그램에 달려 있다고 생각합니다. 몇 명의 사용자를 위해 응용 프로그램을 만들면 차이가 없어야하며 코드는 2 개의 쿼리로 더 읽기 쉽습니다.
Konrad

21
DB가 일치하지 않는 데이터를 저장하고 있다고 가정합니다. 즉, DB와 데이터의 일관성을 믿지 않는 것 같습니다. 이 경우 실제로 큰 문제가 있으며 해결 방법이 있습니다. 완화 용액은 나중에 더 빨리 대체 될 수 있습니다. 통제 및 관리에서 DB를 소비해야하는 경우가있을 수 있습니다. 다른 응용 프로그램에서. 그러한 경우, 나는 그러한 검증을 고려할 것입니다. 어쨌든 @gnasher가 맞습니다. 빠르게 실패하지 않거나 우리가 빨리 실패하는 것으로 이해하는 것이 아닙니다.
Laiv

15

이것은 의견으로 시작되었지만 너무 커졌습니다.

아니요, 다른 답변에서 언급했듯이이 패턴을 사용해서는 안됩니다. *

비동기 구성 요소를 사용하는 시스템을 처리 할 때 데이터베이스 (또는 파일 시스템 또는 기타 비동기 시스템)가 점검과 변경간에 변경 될 수있는 경합 상태 가 항상 있습니다. 이 유형을 확인하는 것은 신뢰할 수있는 방법이 아니며 처리하지 않으려는 오류 유형을 방지합니다.
충분하지 않은 것은 한 눈 에 중복 기록 오류를 방지하여 잘못된 보안 감각을 제공 해야한다는 인상을줍니다 .

어쨌든 오류 처리가 필요합니다.

의견에서 여러 소스의 데이터가 필요한지 묻습니다.
여전히 아님.

근본적인 문제는 당신이 확인하고 싶은 것은 더 복잡하게되면 사라지지 않습니다.

어쨌든 여전히 오류 처리가 필요합니다.

이 검사를 통해 보호하려는 특정 오류를 방지 할 수있는 확실한 방법이더라도 다른 오류가 계속 발생할 수 있습니다. 데이터베이스 연결이 끊어 지거나 공간이 부족하면 어떻게됩니까?

어쨌든 여전히 다른 데이터베이스 관련 오류 처리 가 필요할 것 입니다. 이 특정 오류의 처리는 아마도 작은 부분이어야합니다.

변경 대상을 결정하기 위해 데이터가 필요한 경우, 어딘가에서 데이터를 수집해야합니다. (사용하는 도구에 따라 별도의 쿼리를 수집하는 것보다 더 나은 방법이있을 수 있습니다.) 수집 한 데이터를 검토 할 때 결국 변경을하지 않아도된다고 판단하는 경우에는 변화. 이 결정은 오류 처리 문제와 완전히 별개입니다.

어쨌든 여전히 오류 처리가 필요합니다.

나는 반복적이라는 것을 알고 있지만 이것을 분명히하는 것이 중요하다고 생각합니다. 나는 전에이 혼란을 정리했습니다.

결국 실패 할 것입니다. 그것이 실패하면 바닥에 도달하기가 어렵고 시간이 많이 걸립니다. 경쟁 조건에서 발생하는 문제를 해결하는 것은 어렵습니다. 그것들은 일관되게 일어나지 않기 때문에 단독으로 복제하는 것은 어렵거나 불가능합니다. 시작하기에 적절한 오류 처리를하지 않았으므로 계속 진행해야 할 일은 많지 않을 것입니다. 최종 사용자의 일부 암호 텍스트에 대한 보고서 (처음부터 보지 못하게하려고했습니다). 어쩌면 그것을 볼 때 오류를 가능하게 거부한다는 그 기능을 가리키는 스택 추적 일 수도 있습니다.

*이 작업을 수행하는 데 유효한 비즈니스 이유가있을 수 있습니다 (예 : 응용 프로그램에서 값 비싼 작업이 중복되는 것을 방지). 그러나 적절한 오류 처리를위한 적절한 대체 방법은 아닙니다.


2

여기에 주목해야 할 두 번째 사항은 사용자가 볼 수있는 오류 메시지의 형식을 지정할 수 있기 때문입니다.

나는 진심으로 당신을 추천합니다 :

a) 최종 사용자에게 모든 일반 오류 메시지를 표시 발생하는 오류에 표시합니다.

b) 개발자 만 액세스 할 수있는 (서버에있는 경우) 또는 오류보고 도구 (클라이언트가 배포 된 경우)를 통해 보낼 수있는 실제 예외를 기록합니다.

c) 더 유용한 정보를 추가 할 수 없으면 기록하는 오류 예외 세부 사항을 형식화하지 마십시오. 문제를 추적하는 데 사용할 수있는 유용한 정보를 실수로 '포맷'하지 않으려 고합니다.


요컨대 예외는 매우 유용한 기술 정보로 가득합니다. 이 중 어느 것도 최종 사용자를위한 것이 아니며 위험에 따라이 정보를 잃게됩니다.


2
"최종 사용자에게 발생하는 모든 오류에 대해 동일한 일반 오류 메시지를 표시하십시오." 이것이 최종 사용자를위한 예외 형식을 지정하는 것이 끔찍한 일인 것 같습니다.
Konrad

1
합리적인 데이터베이스 시스템에서 문제가 발생한 이유를 프로그래밍 방식으로 찾을 수 있어야합니다. 예외 메시지를 구문 분석 할 필요는 없습니다. 그리고 더 일반적으로, 누가 오류 메시지를 사용자에게 표시해야한다고 누가 말합니까? 성공할 때까지 (또는 최대 재시도 또는 시간 제한까지) 첫 번째 삽입에 실패하고 루프에서 재 시도 할 수 있습니다. 실제로 백 오프 및 재 시도는 결국 어쨌든 구현하려는 것입니다.
Daniel Pryden
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.