"어떤 ORM을 사용해야합니까?"라는 질문은 대규모 응용 프로그램의 전반적인 데이터 액세스 전략 및 성능 최적화와 관련하여 실제로 거대한 빙산의 일각을 목표로합니다.
데이터베이스 설계 및 유지 관리
이는 데이터 중심 응용 프로그램 또는 웹 사이트의 처리량을 결정하는 가장 중요한 요소 중 하나이며, 프로그래머는이를 완전히 무시합니다.
적절한 정규화 기술을 사용하지 않으면 사이트가 파기됩니다. 기본 키가 없으면 거의 모든 쿼리가 느리게 진행됩니다. 정당한 이유없이 키-값 쌍 (AKA Entity-Attribute-Value)에 테이블을 사용하는 등 잘 알려진 안티 패턴을 사용하는 경우 물리적 읽기 및 쓰기 수를 확장합니다.
페이지 압축, FILESTREAM
스토리지 (이진 데이터의 경우), SPARSE
열, hierarchyid
계층의 경우 등 (모든 SQL Server 예) 과 같이 데이터베이스가 제공하는 기능을 활용하지 않으면 당신 이 볼 수 있는 성능 .
데이터베이스를 설계 한 후 최소한 당분간은 그것이 가능한 한 좋다는 것을 확신 한 후에 데이터 액세스 전략에 대해 걱정해야합니다 .
열성 대 지연 로딩
대부분의 ORM 은 관계에 지연로드 ( lazy loading) 라는 기술을 사용했습니다. 즉, 기본적으로 한 번에 하나의 엔티티 (테이블 행)를로드하고 하나 이상의 관련 (외부)을로드해야 할 때마다 데이터베이스를 왕복합니다. 키) 행.
이것은 좋지 않거나 나쁜 것이 아니라 실제로 데이터로 수행 할 작업과 사전에 얼마나 많이 알고 있는지에 달려 있습니다. 때로는 게으른 로딩이 절대적으로 올바른 일입니다. 예를 들어 NHibernate 는 전혀 쿼리하지 않고 특정 ID에 대한 프록시 를 생성 하기로 결정할 수 있습니다 . ID 만 필요한 경우 왜 더 요청해야합니까? 반면에 3 단계 계층 구조에서 모든 단일 요소의 트리를 인쇄하려고하면 지연로드가 O (N²) 연산이 되어 성능 이 매우 나빠집니다.
"순수한 SQL"(즉, 원시 ADO.NET 쿼리 / 저장 프로 시저)을 사용하면 흥미로운 이점 중 하나는 기본적으로 주어진 화면이나 페이지를 표시하는 데 필요한 데이터를 정확히 생각하게한다는 것입니다. 으로 ORMs 게으른 로딩 기능을하지 않도록 이 일에서 당신을, 그러나 그들은 않는 당신에게 잘 ... 할 수있는 기회 제공 지연 및 실수로 실행 쿼리 수를 폭발합니다. 따라서 ORM의 열성적인 로딩 기능을 이해하고 주어진 페이지 요청에 대해 서버로 전송하는 쿼리 수에주의를 기울여야합니다.
캐싱
모든 주요 ORM은 첫 번째 수준 캐시 인 "일명 캐시"를 유지합니다. 즉, 동일한 엔터티를 ID로 두 번 요청하면 두 번째 왕복이 필요하지 않으며 데이터베이스를 올바르게 설계 한 경우 )를 사용하면 낙관적 동시성을 사용할 수 있습니다.
L1 캐시는 L2S 및 EF에서 매우 불투명하므로 작동하고 있다는 것을 신뢰해야합니다. NHibernate는 그것에 대해 더 명확합니다 ( Get
/ Load
vs. Query
/ QueryOver
). 그럼에도 불구하고 가능한 한 ID로 쿼리하려고 시도하는 한 여기에 좋습니다. 많은 사람들이 L1 캐시를 잊어 버리고 ID 이외의 다른 항목 (예 : 조회 필드)을 통해 동일한 엔티티를 반복해서 반복해서 찾습니다. 이 작업을 수행해야 할 경우 향후 조회를 위해 ID 또는 전체 엔티티를 저장해야합니다.
레벨 2 캐시 ( "쿼리 캐시")도 있습니다. NHibernate는이 기능을 내장하고 있습니다. Linq to SQL 및 Entity Framework는 쿼리 를 컴파일하여 쿼리 표현식 자체를 컴파일하여 앱 서버로드를 상당히 줄일 수 있지만 데이터를 캐시하지는 않습니다. Microsoft는이를 데이터 액세스 문제가 아니라 응용 프로그램 문제로 간주하는 것 같습니다. 이는 L2S와 EF의 주요 단점입니다. "raw"SQL의 약점이기도합니다. 기본적으로 NHibernate 이외의 다른 ORM으로 좋은 성능을 얻으려면 자체 캐싱 외관을 구현해야합니다.
이 EF4에 대한 L2 캐시 "확장자"이기도 괜찮는 응용 프로그램 수준의 캐시 도매 교체는 정말 아니지만.
쿼리 수
관계형 데이터베이스는 데이터 세트 를 기반으로 합니다 . 짧은 시간에 많은 양의 데이터 를 생성하는 데 능숙 하지만 모든 명령에 일정한 양의 오버 헤드가 있기 때문에 쿼리 대기 시간 측면에서 거의 없습니다 . 잘 설계된 앱은이 DBMS의 장점을 활용하고 쿼리 수를 최소화하고 각각의 데이터 양을 최대화해야합니다.
이제 하나의 행만 필요할 때 전체 데이터베이스를 쿼리한다고 말하지 않습니다. 당신이 필요로하는 경우 어떤 말인지이다 Customer
, Address
, Phone
, CreditCard
, 그리고 Order
당신이해야 한 페이지를 제공하기 위해 동시에 행을 모두 에게 동시에 그들 모두에 대해 개별적으로 각 쿼리를 실행하지 않습니다. 때로는 그보다 더 나쁜 경우가 있습니다. 먼저 같은 Customer
레코드를 5 번 연속으로 쿼리하는 코드가 먼저 표시되고 Id
, 그 다음에 Name
, 그 다음에 EmailAddress
.
모두 완전히 다른 데이터 집합에서 작동하는 여러 쿼리를 실행해야하는 경우에도 단일 "스크립트"로 데이터베이스에 모든 쿼리를 보내고 여러 결과 집합을 반환하는 것이 여전히 더 효율적입니다. 총 데이터 양이 아니라 관련된 오버 헤드입니다.
이것은 상식처럼 들릴지 모르지만 응용 프로그램의 다양한 부분에서 실행되는 모든 쿼리를 추적하기가 쉽지 않습니다. 멤버 자격 공급자가 사용자 / 역할 테이블을 쿼리하고, 헤더 작업이 쇼핑 카트를 쿼리하고, 메뉴 작업이 사이트 맵 테이블을 쿼리하고, 사이드 바 작업이 주요 제품 목록을 쿼리 한 다음 페이지가 몇 개의 개별 자율 영역으로 나뉩니다. 주문 내역, 최근 본 항목, 카테고리 및 인벤토리 테이블을 개별적으로 쿼리하고 알기 전에 페이지를 제공하기 전에 20 개의 쿼리를 실행하고 있습니다. 그것은 단지 성능을 완전히 파괴합니다.
NHibernate를 중심으로 생각하는 일부 프레임 워크는 이것에 대해 매우 영리 하며 전체 쿼리를 일괄 처리하고 마지막 순간에 한 번에 모두 실행하려고하는 미래 라는 것을 사용할 수 있습니다 . AFAIK, Microsoft 기술로이 작업을 수행하려는 경우 스스로해야합니다. 애플리케이션 로직에 빌드해야합니다.
인덱싱, 술어 및 프로젝션
내가 말한 개발자의 50 % 이상, 심지어 일부 DBA조차도 인덱스 적용 개념에 문제가있는 것 같습니다. 그들은 " Customer.Name
열이 색인화되어 있으므로 이름에 대한 모든 조회가 빠르다"고 생각합니다. 하지 않는 한 그런 식으로 작동하지 않는 경우를 제외 Name
지수는 커버 당신이 찾고있는 특정 열을. SQL Server에서는 명령문 INCLUDE
에서 수행됩니다 CREATE INDEX
.
SELECT *
프로젝션을 사용하여 명시 적으로 지정하지 않는 한 모든 ORM이 수행하는 작업이 거의 모든 곳에서 순진하게 사용되는 경우 DBMS는 커버되지 않은 열을 포함하기 때문에 인덱스를 완전히 무시하도록 선택할 수 있습니다. 투영은 예를 들어 다음을 수행하는 대신 다음을 의미합니다.
from c in db.Customers where c.Name == "John Doe" select c
대신이 작업을 수행하십시오.
from c in db.Customers where c.Name == "John Doe"
select new { c.Id, c.Name }
그리고이 뜻은, 가장 현대으로 ORMs을 위해, 그것은 단지 가서 조회하도록 지시 Id
하고 Name
아마도 인덱스에 포함되는 열 (그러나 Email
, LastActivityDate
또는 다른 무엇이든 당신이 거기에 붙어 일어난 열).
부적절한 술어를 사용하여 인덱싱 이점을 완전히 없애는 것도 매우 쉽습니다. 예를 들면 다음과 같습니다.
from c in db.Customers where c.Name.Contains("Doe")
... 이전 쿼리와 거의 동일 해 보이지만 실제로로 변환되기 때문에 전체 테이블 또는 인덱스 스캔이 발생합니다 LIKE '%Doe%'
. 마찬가지로 의심스러운 것처럼 보이는 다른 쿼리는 다음과 같습니다.
from c in db.Customers where (maxDate == null) || (c.BirthDate >= maxDate)
에 인덱스가 있다고 가정하면 BirthDate
이 술어는 인덱스를 완전히 쓸모 없게 만들 수 있습니다. 여기서 우리의 가상 프로그래머는 일종의 동적 쿼리 ( "해당 매개 변수가 지정된 경우 생년월일 만 필터링")를 작성하려고 시도했지만 이것이 올바른 방법은 아닙니다. 대신 이렇게 작성 :
from c in db.Customers where c.BirthDate >= (maxDate ?? DateTime.MinValue)
... 이제 DB 엔진은이를 매개 변수화하고 인덱스 탐색을 수행하는 방법을 알고 있습니다. 쿼리 표현식에 대한 사소하고 미미한 변경이 성능에 크게 영향을 줄 수 있습니다.
불행하게도 일반적으로 LINQ가 있기 때문에 모두가 너무 쉽게 이런 나쁜 쿼리를 작성 할 수 있습니다 때로는 공급자가 당신이 쿼리를 최적화하기 위해 노력하고 있었는지 추측 할 수 있으며, 때때로이 없습니다. 그래서 당신 은 평범한 오래된 SQL을 작성했다면 (어쨌든 숙련 된 DBA에게) 맹목적으로 명백했을 실망스러운 일관성없는 결과로 끝납니다 .
기본적으로 모든 것은 생성 된 SQL과 그들이 생성 한 실행 계획을 모두 면밀히 관찰해야한다는 사실에 달려 있으며, 기대하는 결과를 얻지 못하면 무시하지 마십시오. 가끔 ORM 레이어를 작성하고 SQL을 직접 코딩하십시오. 이것은 EF뿐만 아니라 모든 ORM에 적용됩니다.
거래 및 잠금
밀리 초까지 최신 데이터를 표시해야합니까? 어쩌면-그것은 달려 있지만-아마도 아닙니다. 슬프게도, 엔티티 프레임 워크를 제공하지 않습니다nolock
만 사용할 수 있습니다 READ UNCOMMITTED
상기 트랜잭션 레벨 (안 테이블 수준). 실제로 어떤 ORM도 이에 대해 신뢰할만한 것은 없습니다. 더티 읽기를 수행하려면 SQL 레벨로 드롭 다운하고 임시 쿼리 또는 스토어드 프로 시저를 작성해야합니다. 다시 요약하자면 프레임 워크 내에서이를 수행하는 것이 얼마나 쉬운 지입니다.
Entity Framework는 이와 관련하여 먼 길을 왔습니다. EF 버전 1 (.NET 3.5)은 신기한 것으로 "엔티티"추상화를 뚫기가 매우 어려웠지만 이제는 ExecuteStoreQuery 및 Translate 가 있으므로 실제로는 그렇습니다. 나쁘지 않아. 당신이 그들을 많이 사용하기 때문에이 사람들과 친구를 사귀십시오.
쓰기 잠금 및 교착 상태 문제와 데이터베이스에서 잠금을 가능한 한 짧은 시간 동안 유지하는 일반적인 방법도 있습니다. 이와 관련하여, 대부분의 ORM (Entity Framework 포함)은 실제로 작업 단위 패턴 (EF에서 SaveChanges) 을 캡슐화하기 때문에 원시 SQL보다 나은 경향이 있습니다 . 다시 말해, 원할 때마다 엔티티를 "삽입"또는 "업데이트"또는 "삭제"하여 작업 단위를 커밋 할 때까지 변경 사항이 실제로 데이터베이스에 적용되지 않는다는 지식을 확보 할 수 있습니다.
UOW는 장기 실행 트랜잭션과 유사 하지 않습니다 . UOW는 여전히 ORM의 낙관적 동시성 기능을 사용하고 메모리의 모든 변경 사항 을 추적합니다 . 최종 커밋까지 단일 DML 문이 생성되지 않습니다. 이것은 트랜잭션 시간을 가능한 한 낮게 유지합니다. 원시 SQL을 사용하여 애플리케이션을 빌드하는 경우이 지연된 동작을 달성하기가 매우 어렵습니다.
이것이 EF의 구체적 의미 : 작업 단위를 가능한 한 굵게 만들고 절대적으로 필요할 때까지 커밋하지 마십시오. 이렇게하면 임의의 시간에 개별 ADO.NET 명령을 사용할 때보 다 잠금 경합이 훨씬 줄어 듭니다.
EF는 다른 모든 프레임 워크가 트래픽 / 고성능 응용 프로그램에 적합 하듯이 트래픽 / 고성능 응용 프로그램에는 완벽하게 적합합니다. 중요한 것은 사용 방법입니다. 다음은 가장 널리 사용되는 프레임 워크와 성능 측면에서 제공하는 기능에 대한 간단한 비교입니다 (범례 : N = 지원되지 않음, P = 부분, Y = 예 / 지원됨).
보시다시피, EF4 (현재 버전)는 그리 나쁘지 않지만 성능이 주요 관심사 인 경우 아마도 최고가 아닙니다. NHibernate는이 분야에서 훨씬 더 성숙하고 Linq to SQL조차도 EF가 아직 수행하지 못하는 성능 향상 기능을 제공합니다. Raw ADO.NET은 매우 구체적인 데이터 액세스 시나리오에서 종종 더 빠를 것이지만, 모든 부분을 종합하면 다양한 프레임 워크에서 얻을 수있는 많은 중요한 이점을 제공하지 않습니다.