【EFCORE筆記】性能優化方案


談談性能問題

EF Core 是一種快速而令人滿意的ORM 數據訪問框架,但隨着Web 應用程序越來越頻繁的訪問,性能變得越來越重要,但讓人詬病的性能問題一直是很多程序員熱聊的話題,實際情況並不是性能差,而是需要我們掌握如何規     避陷阱和避開影響性能的坑。

純手共執行一個 SQL 語句和用 ORM 框架性能基本相當,只要優化得好,性能問題可以忽略,一般大系統的性能問題都是數據庫設計問題,與使用何種數據訪問技術無直接關系,EF Core 中也可以直接執行 SQL 語句。

 

 

使用上下文實例池

services.AddDbContext<EmployeeContext>(options => options.UseSqlServer(connection));

services.AddDbContextPool<EmployeeContext>(options => options.UseSqlServer(connection));

如果使用 AddDbContextPool 方法,那么在控制器請求DbContext 實例時,我們會首先檢查池中有無可用的實例。 請求處理完成后,實例的任何狀態都將被重置,並且實例本身會返回池中。

從概念上講,此方法類似於ADO.NET 連接池的運行原理,並具有節約DbContext 實例初始化成本的優勢。

https://docs.microsoft.com/zh-cn/ef/core/what-is-new/ef-core-2.0#dbcontext-pooling

Use DbContextPooling to improve the performance: .Net Core 2.1 feature

 

 

顯式編譯查詢

EF 早期版本以及 LINQ to SQL 中已經提供手動或顯式編譯的查詢API,允許應用程序緩存查詢轉換,使其可僅被計算一次並執行多次。

盡管 EF Core 通常可基於查詢表達式的哈希表示法自動編譯和緩存查詢,但是使用此機制可繞過哈希計算和緩存查詢,允許應用程序通過調用委托來使用已編譯查詢,從而實現性能小幅提升。

// Create an explicitly compiled query
private static Func<CustomerContext, int, Customer> _customerById =
        EF.CompileQuery((CustomerContext db, int id) =>
                db.Customers
                        .Include(c => c.Address)
                        .Single(c => c.Id == id));

// Use the compiled query by invoking it using (var db = new CustomerContext())
{
      var customer = _customerById(db, 147);

}

  測試 EF Core 使用編譯查詢提高性能

 

多活動結果集數據庫連接復用

多活動結果集 (MARS) 是一項允許對單個連接執行多個批處理的功能。 在以前的版本中,在單個連接上一次只能執行一個批處理。 使用 MARS 執行多個批處理並不意味着同時執行操作。

在連接字符串中添加:MultipleActiveResultSets=True 即可啟用 MARS 特性。

Multiple Active Result Sets (MARS)

如果未啟用 MARS 連接復用,多次打開應用程序,在 SQL Server Management Studio 中使用 sp_who 命令會查詢當前存在多個活動的連接。

如果啟用了 MARS 連接復用,進行上述操作,發現只有一個活動連接訪問數據庫,因為連接被復用。

 

優先使用Async 異步方法

EF Core 中提供了很多形如xxxAsync 的異步方法,推薦使用這些方法提高吞吐量和性能,減少不必要的延時等待。

 

批處理語句

optionbuilder.UseSqlServer(sConnString , b => b.MaxBatchSize(1));
//默認是批處理,這樣配置是關閉批處理

Entity Framework Core 批處理語句性能測試

    

添加索引提高數據查詢性能

索引是對數據庫表中一列或多列的值進行排序的一種結構,使用索引可快速訪問數據庫表中的特定信息。

modelBuilder.Entity<Blog>().HasIndex(b => b.Url).IsUnique(); 

modelBuilder.Entity<Person>()
.HasIndex(p => new { p.FirstName, p.LastName });

 

避免N+1 查詢

模型中可使用導航屬性來加載相關實體。 有三種常見的 O/RM 模式可用於加載關聯數據。預先加載表示從數據庫中加載關聯數據,作為初始查詢的一部分。顯式加載表示稍后從數據庫中顯式加載關聯數據。延遲加載表示在訪問     導航屬性時,從數據庫中以透明方式加載關聯數據。

var blogs = context.Blogs.Include(blog => blog.Posts)
.ThenInclude(post => post.Author).ToList();

  但是,任何技術都有兩面性,優勢不可能讓你占完了,使用N+1 模式的優點是可以單獨緩存部分數據。

 

跟蹤與非跟蹤查詢

跟蹤行為決定了 EF Core 是否將有關實體實例的快照信息保留在其更改跟蹤器中。 如果已跟蹤某個實體,則該實體中檢測到的任何更改都會在SaveChanges() 期間永久保存到數據庫。

當決定只查詢數據,不更改數據時,非跟蹤查詢十分有用,非跟蹤查詢的執行會更快,因為無需為查詢實體設置快     照跟蹤信息。 如果不需要更新從數據庫中檢索到的實體,則應優先使用非跟蹤查詢。

var blogs = context.Blogs.AsNoTracking().ToList(); 

context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;

  

關閉 DetectChanges 狀態同步

當從數據庫進行查詢數據時,上下文便捕獲了每個實體屬性的快照(數據庫值,原始值,當前值),當調用SaveChanges 時,在內部會自動調用 DetectChanges 方法,此方法將掃描上下文中所有實體,並比較當前屬性值和存儲在快照中的原始屬性值,如果被找到的屬性值發生了改變,此時EF將會與數據庫進行交互,進行數據更      新。

會導致自動調用 DetectChanges 方法: Find、Local、Remove、Add、Update、Attach、SaveChanges 和

Entry 等。

但是,自動同步狀態會頻繁調用,可手動關閉以上方法的自動同步,當數據都修改好后,一次性手動同步。

context.ChangeTracker.AutoDetectChangesEnabled = false;

//執行操作后手動同步狀態
context.ChangeTracker.DetectChanges();

  

數據緩存

緩存可以減少重復內容的多次生成成本,從而顯著提高應用程序的性能和可伸縮性。     緩存最適用於不經常更改的數據,生成成本很高的數據。 通過緩存,可以比從數據源返回的數據的副本速度快得多。

EF Core 的高性能二級查詢緩存開源庫:EntityFrameworkCore.Cacheable

var cacheableQuery = cacheableContext.Books
    .Include(d => d.Pages)
    .ThenInclude(d => d.Lines)
    .Where(d => d.ID == 200)
    .Cacheable(TimeSpan.FromSeconds(60));

  

使用 EF.Functions.Link 模糊查詢

EF Core 支持使用 StartsWith、Contains  EndsWith 方法進行模糊查詢,這些方法被翻譯成 LIKE 語句,但為了提高性能也提供了一個EF.Functions.Like 方式,這種方式生成的 SQL 語句性能更優。

var result= context.Blogs
.Where(b => EF.Functions.Like(b.BlogName, "%xcode%"))

  Entity Framework Core Like 查詢揭秘

 

DbFunctionAttribute 標量函數

EF Core 支持映射數據庫中定義的函數,可以在LINQ 查詢中使用,該功能支持將數據庫標量函數映射到方法存根, 使其可用於LINQ 查詢並轉換為 SQL。

DbContext 上聲明靜態方法,並使用 DbFunctionAttribute 對其批注:

public class BloggingContext : DbContext
{
        [DbFunction]
        public static int PostReadCount(int blogId)
        {
                throw new Exception();
        }
}

  此類方法會自動注冊,對LINQ 查詢方法的調用可轉換為SQL 中的函數調用:

var query =context.Posts.Where(p=>BloggingContext.PostReadCount(p.Id) > 5)

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM