효율적으로 쿼리하는 것은 인덱스, 관련 엔터티 로드 전략 등 광범위한 주제를 다루는 방대한 주제입니다. 이 섹션에서는 쿼리를 더 빠르게 만들기 위한 몇 가지 일반적인 테마와 일반적으로 발생하는 문제를 자세히 설명합니다.
인덱스를 올바르게 사용
쿼리가 빠르게 실행되는지 여부의 주요 결정 요소는 적절한 경우 인덱스를 제대로 활용할지 여부입니다. 데이터베이스는 일반적으로 많은 양의 데이터를 보유하는 데 사용되며, 전체 테이블을 트래버스하는 쿼리는 일반적으로 심각한 성능 문제의 원인입니다. 지정된 쿼리에서 인덱스 사용 여부를 즉시 알 수 없으므로 인덱싱 문제를 쉽게 파악할 수 없습니다. 다음은 그 예입니다.
// Matches on start, so uses an index (on SQL Server)
var posts1 = await context.Posts.Where(p => p.Title.StartsWith("A")).ToListAsync();
// Matches on end, so does not use the index
var posts2 = await context.Posts.Where(p => p.Title.EndsWith("A")).ToListAsync();
인덱싱 문제를 파악하는 좋은 방법은 먼저 느린 쿼리를 정확히 파악한 다음 데이터베이스에서 선호하는 도구를 통해 쿼리 계획을 검사하는 것입니다. 이 작업을 수행하는 방법에 대한 자세한 내용은 성능 진단 페이지를 참조하세요. 쿼리 계획은 쿼리가 전체 테이블을 트래버스하는지 아니면 인덱스를 사용하는지 여부를 표시합니다.
일반적으로 인덱스를 사용하거나 인덱스와 관련된 성능 문제를 진단하는 특별한 EF 지식은 없습니다. 인덱스와 관련된 일반 데이터베이스 지식은 EF를 사용하지 않는 애플리케이션과 마찬가지로 EF 애플리케이션과 관련이 있습니다. 다음은 인덱스를 사용할 때 유의해야 할 몇 가지 일반적인 지침을 나열합니다.
- 인덱스는 쿼리 속도를 높일 수 있지만 up-to유지해야 하므로 업데이트 속도가 느려집니다. 필요하지 않은 인덱스를 정의하지 말고 인 덱스 필터를 사용하여 인덱스를 행의 하위 집합으로 제한하여 이 오버헤드를 줄이는 것이 좋습니다.
- 복합 인덱스는 여러 열을 필터링하는 쿼리 속도를 높일 수 있지만 순서에 따라 모든 인덱스의 열을 필터링하지 않는 쿼리의 속도를 높일 수도 있습니다. 예를 들어 A 및 B 열의 인덱스는 A 및 B로만 필터링하는 쿼리뿐만 아니라 A로만 필터링하는 쿼리의 속도를 높일 수 있지만 B를 통해서만 쿼리 필터링 속도를 높일 수는 없습니다.
- 쿼리가 열에 대한 식으로 필터링하는 경우(예:
price / 2
) 단순 인덱스를 사용할 수 없습니다. 그러나 식에 대해 저장된 지속형 열을 정의하고 이를 통해 인덱싱을 만들 수 있습니다. 일부 데이터베이스는 식 인덱스도 지원하므로 식별로 쿼리 필터링 속도를 높이기 위해 직접 사용할 수 있습니다. - 다른 데이터베이스를 사용하면 다양한 방법으로 인덱스를 구성할 수 있으며, 대부분의 경우 EF Core 공급자는 Fluent API를 통해 이러한 인덱스를 노출합니다. 예를 들어 SQL Server 공급자를 사용하면 인덱스가 클러스터형인지 아니면 채우기 비율을 설정할지 구성할 수 있습니다. 자세한 내용은 공급자 설명서를 참조하세요.
필요한 프로젝트 전용 속성
EF Core를 사용하면 엔터티 인스턴스를 매우 쉽게 쿼리한 다음 코드에서 해당 인스턴스를 사용할 수 있습니다. 그러나 엔터티 인스턴스를 쿼리하면 데이터베이스에서 필요한 것보다 더 많은 데이터를 가져오는 경우가 많습니다. 다음을 살펴보세요.
await foreach (var blog in context.Blogs.AsAsyncEnumerable())
{
Console.WriteLine("Blog: " + blog.Url);
}
이 코드에는 실제로 각 블로그의 Url
속성만 필요하지만 전체 블로그 엔터티가 페치되고 불필요한 열이 데이터베이스에서 전송됩니다.
SELECT [b].[BlogId], [b].[CreationDate], [b].[Name], [b].[Rating], [b].[Url]
FROM [Blogs] AS [b]
프로젝션할 열을 EF에 Select
알려서 최적화할 수 있습니다.
await foreach (var blogName in context.Blogs.Select(b => b.Url).AsAsyncEnumerable())
{
Console.WriteLine("Blog: " + blogName);
}
결과 SQL은 필요한 열만 다시 가져옵니다.
SELECT [b].[Url]
FROM [Blogs] AS [b]
둘 이상의 열을 프로젝션해야 하는 경우 원하는 속성을 사용하여 C# 익명 형식으로 프로젝션합니다.
이 기술은 읽기 전용 쿼리에 매우 유용하지만, EF의 변경 내용 추적은 엔터티 인스턴스에서만 작동하므로 가져온 블로그를 업데이트 해야 하는 경우 상황이 더 복잡해집니다. 수정된 블로그 인스턴스를 연결하고 EF에 변경된 속성을 알려 전체 엔터티를 로드하지 않고도 업데이트를 수행할 수 있지만, 이는 가치가 없을 수 있는 고급 기술입니다.
결과 집합 크기 제한
기본적으로 쿼리는 필터와 일치하는 모든 행을 반환합니다.
var blogsAll = await context.Posts
.Where(p => p.Title.StartsWith("A"))
.ToListAsync();
반환되는 행 수는 데이터베이스의 실제 데이터에 따라 달라지므로 데이터베이스에서 로드되는 데이터의 양, 결과에서 차지되는 메모리 양, 이러한 결과를 처리할 때 생성되는 추가 부하(예: 네트워크를 통해 사용자 브라우저로 전송)를 알 수 없습니다. 결정적으로 테스트 데이터베이스에는 데이터가 거의 없으므로 테스트하는 동안 모든 것이 잘 작동하지만 쿼리가 실제 데이터에서 실행되기 시작하고 많은 행이 반환되면 성능 문제가 갑자기 나타납니다.
결과적으로, 일반적으로 결과의 수를 제한하는 것을 고려하는 것이 가치가 있다.
var blogs25 = await context.Posts
.Where(p => p.Title.StartsWith("A"))
.Take(25)
.ToListAsync();
최소한 UI는 데이터베이스에 더 많은 행이 있을 수 있음을 나타내는 메시지를 표시할 수 있습니다(그리고 다른 방식으로 검색할 수 있음). 본격적인 솔루션은 페이지 매김을 구현합니다. 여기서 UI는 한 번에 특정 개수의 행만 표시하고 사용자가 필요에 따라 다음 페이지로 진행할 수 있도록 합니다. 이 작업을 효율적으로 구현하는 방법에 대한 자세한 내용은 다음 섹션을 참조하세요.
효율적인 페이지 매김
페이지네이션은 한 번에 모든 결과를 검색하는 대신, 결과를 페이지 단위로 검색하는 것을 의미합니다. 이는 주로 큰 결과 집합에서 사용자가 결과의 다음 페이지 또는 이전 페이지로 이동할 수 있도록 하는 사용자 인터페이스가 표시될 때 수행됩니다. 데이터베이스에서 페이지 매김을 구현하는 일반적인 방법은 SQL에서 Skip
및 Take
연산자(OFFSET
및 LIMIT
)를 사용하는 것입니다. 이는 직관적인 구현이지만 매우 비효율적입니다. 임의의 페이지로 이동하는 대신 한 번에 한 페이지를 이동할 수 있는 페이지 매김의 경우 대신 키 집합 페이지 매김 을 사용하는 것이 좋습니다.
자세한 내용은 페이지 매김에 대한 설명서 페이지를 참조하세요.
관련 엔터티를 로드할 때 카티전 폭발 방지
관계형 데이터베이스에서 모든 관련 엔터티는 단일 쿼리에 JOIN을 도입하여 로드됩니다.
SELECT [b].[BlogId], [b].[OwnerId], [b].[Rating], [b].[Url], [p].[PostId], [p].[AuthorId], [p].[BlogId], [p].[Content], [p].[Rating], [p].[Title]
FROM [Blogs] AS [b]
LEFT JOIN [Post] AS [p] ON [b].[BlogId] = [p].[BlogId]
ORDER BY [b].[BlogId], [p].[PostId]
일반적인 블로그에 관련된 게시물이 여러 개인 경우 이러한 게시물에 대한 행이 블로그의 정보를 복제합니다. 이 중복은 소위 "카티전 폭발"문제로 이어집니다. 일 대 다 관계가 로드되면 중복된 데이터의 양이 증가하여 애플리케이션의 성능에 부정적인 영향을 줄 수 있습니다.
EF를 사용하면 별도의 쿼리를 통해 관련 엔터티를 로드하는 "분할 쿼리"를 사용하여 이 효과를 방지할 수 있습니다. 자세한 내용은 분할 및 단일 쿼리에 대한 설명서를 참조하세요.
비고
분할 쿼리의 현재 구현은 각 쿼리에 대한 왕복을 실행합니다. 향후 이를 개선하고 모든 쿼리를 단일 왕복으로 실행할 계획입니다.
가능한 경우 관련 엔터티를 즉시 로드하십시오.
이 섹션을 계속하기 전에 관련 엔터티의 전용 페이지를 읽는 것이 좋습니다.
관련 엔터티를 처리할 때 일반적으로 로드해야 하는 사항을 미리 알고 있습니다. 일반적인 예는 모든 게시물과 함께 특정 블로그 집합을 로드하는 것입니다. 이러한 시나리오에서는 EF가 필요한 모든 데이터를 한 라운드트립으로 가져올 수 있도록 항상 즉시 로드를 사용하는 것이 좋습니다. 필터링된 포함 기능을 사용하면 로드할 관련 엔터티를 제한하면서 로드 프로세스를 즉시 유지하므로 단일 왕복에서 수행할 수 있습니다.
using (var context = new BloggingContext())
{
var filteredBlogs = await context.Blogs
.Include(
blog => blog.Posts
.Where(post => post.BlogId == 1)
.OrderByDescending(post => post.Title)
.Take(5))
.ToListAsync();
}
다른 시나리오에서는 주 엔터티를 가져오기 전에 어떤 관련 엔터티가 필요한지 알 수 없습니다. 예를 들어 일부 블로그를 로드할 때 해당 블로그의 게시물에 관심이 있는지 여부를 알기 위해 다른 데이터 원본(웹 서비스)을 참조해야 할 수 있습니다. 이러한 경우 명시적 또는 지연 로드를 사용하여 관련 엔터티를 별도로 가져온 후, 블로그의 게시물 내비게이션을 채울 수 있습니다. 이 메서드는 즉시 실행되지 않으므로 데이터베이스에 대한 추가 왕복이 필요하여 속도가 느려집니다. 특정 시나리오에서는 필요한 게시물만 선택적으로 가져오는 대신 모든 게시물을 항상 로드하는 것이 더 효율적일 수 있습니다.
지연 로드에 주의하세요
지연 로드는 코드가 데이터베이스의 관련 엔터티에 접근할 때 EF Core가 자동으로 이를 로드하기 때문에, 데이터베이스 논리를 구현하는 데 매우 유용한 방법으로 보입니다. 이렇게 하면 필요하지 않은 관련 엔터티( 예: 명시적 로드)를 로드하지 않고 프로그래머가 관련 엔터티를 모두 처리하지 않아도 됩니다. 지연 로딩은 불필요한 추가 요청을 발생시켜 애플리케이션을 느리게 할 수 있습니다.
다음을 살펴보세요.
foreach (var blog in await context.Blogs.ToListAsync())
{
foreach (var post in blog.Posts)
{
Console.WriteLine($"Blog {blog.Url}, Post: {post.Title}");
}
}
이 단순한 코드는 모든 블로그와 게시물을 반복하며 출력합니다. EF Core의 문장 로깅을 켜면 다음이 표시됩니다.
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (1ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT [b].[BlogId], [b].[Rating], [b].[Url]
FROM [Blogs] AS [b]
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (5ms) [Parameters=[@__p_0='1'], CommandType='Text', CommandTimeout='30']
SELECT [p].[PostId], [p].[BlogId], [p].[Content], [p].[Title]
FROM [Post] AS [p]
WHERE [p].[BlogId] = @__p_0
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (1ms) [Parameters=[@__p_0='2'], CommandType='Text', CommandTimeout='30']
SELECT [p].[PostId], [p].[BlogId], [p].[Content], [p].[Title]
FROM [Post] AS [p]
WHERE [p].[BlogId] = @__p_0
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (1ms) [Parameters=[@__p_0='3'], CommandType='Text', CommandTimeout='30']
SELECT [p].[PostId], [p].[BlogId], [p].[Content], [p].[Title]
FROM [Post] AS [p]
WHERE [p].[BlogId] = @__p_0
... and so on
무슨 일이 일어나고 있는 건가요? 위의 간단한 루프에 대해 이러한 모든 쿼리가 전송되는 이유는 무엇인가요? 지연 로드를 사용하면 블로그의 게시물 속성에 액세스할 때만 게시물이 지연 로드됩니다. 그 결과, 내부 foreach 루프의 각 반복마다 별도의 왕복을 통해 추가 데이터베이스 쿼리가 발생합니다. 따라서 초기 쿼리에서 모든 블로그를 로드한 후 블로그당 다른 쿼리를 사용하여 모든 게시물을 로드합니다. 이를 N+1 문제라고도 하며 매우 중요한 성능 문제를 일으킬 수 있습니다.
블로그의 모든 게시물이 필요하다고 가정하면, 이 경우에는 시급 로드를 사용하는 것이 더 좋습니다. Include 연산자를 사용하여 로드를 수행할 수 있지만 블로그의 URL만 필요하므로 필요한 항목만 로드해야 합니다. 따라서 프로젝션을 대신 사용합니다.
await foreach (var blog in context.Blogs.Select(b => new { b.Url, b.Posts }).AsAsyncEnumerable())
{
foreach (var post in blog.Posts)
{
Console.WriteLine($"Blog {blog.Url}, Post: {post.Title}");
}
}
이렇게 하면 EF Core가 게시물과 함께 모든 블로그를 단일 쿼리로 가져옵니다. 경우에 따라 분할 쿼리를 사용하여 카티전 폭발 효과를 방지하는 것이 유용할 수도 있습니다.
경고
지연 로드를 사용하면 실수로 N+1 문제가 발생하기 쉬우므로 이를 피하는 것이 좋습니다. 즉시 로드 또는 명시적 로드는 데이터베이스 왕복이 발생할 때 소스 코드에서 명확하게 드러나게 합니다.
버퍼링 및 스트리밍
버퍼링은 모든 쿼리 결과를 메모리에 로드하는 것을 의미하지만 스트리밍은 EF가 매번 단일 결과를 애플리케이션에 전달하는 반면, 메모리에 전체 결과 집합을 포함하지 않습니다. 원칙에 따라 스트리밍 쿼리의 메모리 요구 사항은 고정됩니다. 쿼리가 1행 또는 1000을 반환하는지 여부와 동일합니다. 반면에 버퍼링 쿼리에는 더 많은 행이 반환되는 메모리가 필요합니다. 큰 결과 집합을 생성하는 쿼리의 경우 중요한 성능 요소가 될 수 있습니다.
쿼리 버퍼 또는 스트림은 평가 방법에 따라 달라집니다.
// ToList and ToArray cause the entire resultset to be buffered:
var blogsList = await context.Posts.Where(p => p.Title.StartsWith("A")).ToListAsync();
var blogsArray = await context.Posts.Where(p => p.Title.StartsWith("A")).ToArrayAsync();
// Foreach streams, processing one row at a time:
await foreach (var blog in context.Posts.Where(p => p.Title.StartsWith("A")).AsAsyncEnumerable())
{
// ...
}
// AsAsyncEnumerable also streams, allowing you to execute LINQ operators on the client-side:
var doubleFilteredBlogs = context.Posts
.Where(p => p.Title.StartsWith("A")) // Translated to SQL and executed in the database
.AsAsyncEnumerable()
.Where(p => SomeDotNetMethod(p)); // Executed at the client on all database results
쿼리에서 몇 가지 결과만 반환하는 경우 이에 대해 걱정할 필요가 없습니다. 그러나 쿼리가 많은 수의 행을 반환할 수 있는 경우 버퍼링 대신 스트리밍에 대한 생각을 할 가치가 있습니다.
비고
ToList 또는 ToArray을 사용하고 결과에 다른 LINQ 연산자를 적용하려고 하지 마세요. 이렇게 하면 모든 결과가 메모리에 불필요하게 버퍼링됩니다. AsEnumerable를 대신 사용하세요.
EF의 내부 버퍼링
특정 상황에서 EF는 쿼리를 평가하는 방법에 관계없이 결과 집합을 내부적으로 버퍼링합니다. 이 경우 두 가지는 다음과 같습니다.
- 재시도 실행 전략이 있는 경우 이 작업은 나중에 쿼리를 다시 시도하면 동일한 결과가 반환되도록 하기 위해 수행됩니다.
- 분할 쿼리를 사용하면 MARS(다중 활성 결과 집합)가 SQL Server에서 활성화되지 않는 한 마지막 쿼리를 제외한 모든 쿼리의 결과 집합이 버퍼링됩니다. 여러 쿼리 결과 집합을 동시에 활성화하는 것은 일반적으로 불가능하기 때문입니다.
이 내부 버퍼링은 LINQ 연산자로 인해 발생하는 버퍼링 외에도 추가로 발생합니다. 예를 들어 ToList를 쿼리에 사용하고 다시 시도 실행 전략이 있는 경우, 결과 집합은 EF에서 내부적으로 한 번, 그리고 에 의해 한 번, 총 ToList 메모리에 로드됩니다.
추적, 추적 없음 및 ID 확인
이 섹션을 계속하기 전에 추적 및 추적 없음에 대한 전용 페이지를 읽는 것이 좋습니다.
EF는 기본적으로 엔터티 인스턴스를 추적하므로, 엔터티 인스턴스에 대한 변경 내용이 감지되고 SaveChanges가 호출될 때 저장됩니다. 쿼리 추적의 또 다른 효과는 EF가 데이터에 대해 인스턴스가 이미 로드되었는지 감지하고 새 인스턴스를 반환하지 않고 추적된 인스턴스를 자동으로 반환한다는 것입니다. 이를 ID 확인이라고 합니다. 성능 관점에서 변경 내용 추적은 다음을 의미합니다.
- EF는 내부적으로 추적된 인스턴스의 사전을 유지 관리합니다. 새 데이터가 로드되면 EF는 사전을 검사하여 인스턴스가 해당 엔터티의 키(ID 확인)에 대해 이미 추적되었는지 확인합니다. 사전 유지 관리 및 조회는 쿼리 결과를 로드하는 데 다소 시간이 소요됩니다.
- 로드된 인스턴스를 애플리케이션에 전달하기 전에 EF는 해당 인스턴스 를 스냅샷 하고 스냅샷을 내부적으로 유지합니다. SaveChanges 호출되면 애플리케이션의 인스턴스를 스냅샷과 비교하여 유지할 변경 내용을 검색합니다. 스냅샷은 더 많은 메모리를 차지하며 스냅샷 프로세스 자체는 시간이 걸립니다. 값 비교자를 통해 다른 보다 효율적인 스냅샷 동작을 지정하거나 변경 추적 프록시를 사용하여 스냅샷 프로세스를 완전히 우회할 수 있습니다(하지만 고유한 단점이 있음).
변경 내용이 데이터베이스에 다시 저장되지 않는 읽기 전용 시나리오에서는 추적 없음 쿼리를 사용하여 위의 오버헤드를 방지할 수 있습니다. 그러나 추적 없음 쿼리는 ID 확인을 수행하지 않으므로 로드된 다른 여러 행에서 참조하는 데이터베이스 행이 다른 인스턴스로 구체화됩니다.
설명하기 위해 데이터베이스에서 많은 수의 게시물과 각 게시물에서 참조하는 블로그를 로드하고 있다고 가정합니다. 100개의 게시물이 동일한 블로그를 참조하는 경우 추적 쿼리는 ID 확인을 통해 이를 검색하고 모든 Post 인스턴스는 중복되지 않은 동일한 블로그 인스턴스를 참조합니다. 반면 추적 없음 쿼리는 동일한 블로그를 100번 복제하며 애플리케이션 코드를 그에 따라 작성해야 합니다.
다음은 각 게시물이 20개인 10개의 블로그를 로드하는 쿼리에 대한 추적 및 추적 없음 동작을 비교하는 벤치마크 결과입니다. 소스 코드는 여기에서 사용할 수 있으며, 사용자 고유의 측정을 위한 기준으로 자유롭게 사용할 수 있습니다.
메서드 | NumBlogs | 블로그당 게시물 수 | 평균 | 오류 | StdDev | 중앙값 | 비율 | RatioSD | Gen 0 | Gen 1 | Gen 2 | 할당 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
AsTracking | 10 | 20 | 1,414.7 우리 | 27.20 USD | 45.44 당사 | 1,405.5 us | 1.00 | 0.00 | 60.5469 | 13.6719 | - | 380.11KB |
AsNoTracking | 10 | 20 | 993.3 우리 | 24.04 ㎲ | 65.40 우리 | 966.2 당사 | 0.71 | 0.05 | 37.1094 | 6.8359 | - | 232.89KB |
마지막으로 추적 없음 쿼리를 활용한 다음 반환된 인스턴스를 컨텍스트에 연결하여 변경 내용을 지정하여 변경 내용 추적의 오버헤드 없이 업데이트를 수행할 수 있습니다. 이렇게 하면 변경 내용 추적의 부담이 EF에서 사용자에게 전송되며, 프로파일링 또는 벤치마킹을 통해 변경 내용 추적 오버헤드가 허용되지 않는 것으로 표시된 경우에만 시도해야 합니다.
SQL 쿼리 사용
경우에 따라 EF가 생성하지 않는 쿼리에 대해 더 최적화된 SQL이 존재합니다. SQL 구문이 지원되지 않는 데이터베이스와 관련된 확장이거나 EF가 아직 변환되지 않았기 때문에 발생할 수 있습니다. 이러한 경우 SQL을 직접 작성하면 상당한 성능 향상을 제공할 수 있으며 EF는 이 작업을 수행하는 여러 가지 방법을 지원합니다.
- 쿼리에서 SQL 쿼리를 직접 사용합니다, 예를 들어 를 통해 가능합니다. EF는 일반 LINQ 쿼리를 SQL과 함께 사용하여 작성할 수 있도록 하며, 그 결과 쿼리의 일부를 SQL로 표현할 수 있습니다. 이는 SQL을 코드베이스의 단일 쿼리에서만 사용해야 하는 경우에 좋은 기술입니다.
- UDF( 사용자 정의 함수 )를 정의한 다음 쿼리에서 호출합니다. EF를 사용하면 사용자 정의 함수(UDF)가 전체 결과 집합을 반환할 수 있습니다. 이는 테이블 반환 함수(TVF)로 알려져 있으며,
DbSet
을 함수에 매핑하여 마치 다른 테이블과 동일하게 보이도록 할 수 있습니다. - 쿼리에서 데이터베이스 뷰 및 쿼리를 정의합니다. 함수와 달리 뷰는 매개 변수를 수락할 수 없습니다.
비고
원시 SQL은 일반적으로 EF가 원하는 SQL을 생성할 수 없는지 확인하고 지정된 쿼리가 이를 정당화할 만큼 성능이 중요한 경우 최후의 수단으로 사용해야 합니다. 원시 SQL을 사용하면 상당한 유지 관리 단점이 있습니다.
비동기 프로그래밍
일반적으로 애플리케이션의 확장성을 위해 항상 동기 API(예: 대신SaveChangesAsync)가 아닌 비동기 API를 사용하는 것이 중요합니다SaveChanges. 동기 API는 데이터베이스 I/O 기간 동안 스레드를 차단하므로 스레드에 대한 필요성과 발생해야 하는 스레드 컨텍스트 스위치의 수가 증가합니다.
자세한 내용은 비동기 프로그래밍 페이지를 참조하세요.
경고
동일한 애플리케이션에서 동기 및 비동기 코드를 혼합하지 마세요. 미묘한 스레드 풀 고갈 문제를 실수로 트리거하는 것은 매우 쉽습니다.
경고
Microsoft.Data.SqlClient 비동기 구현에는 알려진 문제(예: #593, #601등)가 있습니다. 예기치 않은 성능 문제가 발생하는 경우 특히 큰 텍스트 또는 이진 값을 처리할 때 동기화 명령 실행을 대신 사용해 보세요.
추가 리소스
- 효율적인 쿼리와 관련된 추가 항목은 고급 성능 항목 페이지를 참조하세요.
- nullable 값을 비교할 때 몇 가지 모범 사례는 null 비교 설명서 페이지의 성능 섹션 을 참조하세요.
.NET