tl;dr Beberapa Include
s meledakkan set hasil SQL. Segera menjadi lebih murah untuk memuat data dengan beberapa panggilan basis data daripada menjalankan satu pernyataan besar. Cobalah untuk menemukan campuran terbaik dari Include
dan Load
pernyataan.
sepertinya ada penalti kinerja saat menggunakan Sertakan
Itu pernyataan yang meremehkan! Beberapa Include
s dengan cepat meledakkan hasil kueri SQL baik lebar maupun panjangnya. Mengapa demikian?
Faktor pertumbuhan Include
s
(Bagian ini menerapkan Entity Framework klasik, v6 dan sebelumnya)
Katakanlah kita memiliki
- entitas root
Root
- entitas induk
Root.Parent
- entitas anak
Root.Children1
danRoot.Children2
- pernyataan LINQ
Root.Include("Parent").Include("Children1").Include("Children2")
Ini membangun pernyataan SQL yang memiliki struktur berikut:
SELECT *, <PseudoColumns>
FROM Root
JOIN Parent
JOIN Children1
UNION
SELECT *, <PseudoColumns>
FROM Root
JOIN Parent
JOIN Children2
Ini <PseudoColumns>
terdiri dari ekspresi seperti CAST(NULL AS int) AS [C2],
dan mereka berfungsi untuk memiliki jumlah kolom yang sama di semua UNION
-ed pertanyaan. Bagian pertama menambahkan kolom semu untuk Child2
, bagian kedua menambahkan kolom semu untuk Child1
.
Inilah yang dimaksud dengan ukuran set hasil SQL:
- Jumlah kolom di
SELECT
klausa adalah jumlah semua kolom dalam empat tabel - Jumlah baris adalah jumlah record dalam koleksi anak yang disertakan
Karena jumlah total titik data adalah columns * rows
, setiap tambahan Include
secara eksponensial meningkatkan jumlah total titik data dalam kumpulan hasil. Biarkan saya menunjukkannya dengan mengambil Root
lagi, sekarang dengan tambahan Children3
koleksi. Jika semua tabel memiliki 5 kolom dan 100 baris, kita peroleh:
Satu Include
(Root
+ 1 koleksi anak):10 kolom * 100 baris =1000 titik data.
Dua Include
s (Root
+ 2 koleksi anak):15 kolom * 200 baris =3000 titik data.
Tiga Include
s (Root
+ 3 koleksi anak):20 kolom * 300 baris =6000 titik data.
Dengan 12 Includes
ini akan berjumlah 78000 poin data!
Sebaliknya, jika Anda mendapatkan semua catatan untuk setiap tabel secara terpisah, bukan 12 Includes
, Anda memiliki 13 * 5 * 100
poin data:6500, kurang dari 10%!
Sekarang angka-angka ini agak dilebih-lebihkan karena banyak dari titik data ini akan menjadi null
, jadi mereka tidak berkontribusi banyak pada ukuran sebenarnya dari kumpulan hasil yang dikirim ke klien. Tetapi ukuran kueri dan tugas untuk pengoptimal kueri tentu saja terpengaruh secara negatif dengan meningkatnya jumlah Include
s.
Saldo
Jadi menggunakan Includes
adalah keseimbangan antara biaya panggilan database dan volume data. Sulit untuk memberikan aturan praktis, tetapi sekarang Anda dapat membayangkan bahwa volume data umumnya dengan cepat melebihi biaya panggilan tambahan jika ada lebih dari ~3 Includes
untuk koleksi anak (tetapi sedikit lebih banyak untuk induk Includes
, yang hanya memperlebar kumpulan hasil).
Alternatif
Alternatif untuk Include
adalah memuat data dalam kueri terpisah:
context.Configuration.LazyLoadingEnabled = false;
var rootId = 1;
context.Children1.Where(c => c.RootId == rootId).Load();
context.Children2.Where(c => c.RootId == rootId).Load();
return context.Roots.Find(rootId);
Ini memuat semua data yang diperlukan ke dalam cache konteks. Selama proses ini, EF menjalankan perbaikan hubungan yang secara otomatis mengisi properti navigasi (Root.Children
dll.) oleh entitas yang dimuat. Hasil akhirnya identik dengan pernyataan dengan Include
s, kecuali untuk satu perbedaan penting:koleksi anak tidak ditandai sebagai dimuat di pengelola status entitas, jadi EF akan mencoba memicu pemuatan lambat jika Anda mengaksesnya. Itulah mengapa penting untuk menonaktifkan pemuatan lambat.
Pada kenyataannya, Anda harus mencari tahu kombinasi mana dari Include
dan Load
pernyataan paling cocok untuk Anda.
Aspek lain yang perlu dipertimbangkan
Setiap Include
juga meningkatkan kompleksitas kueri, sehingga pengoptimal kueri basis data harus berupaya lebih keras untuk menemukan rencana kueri terbaik. Pada titik tertentu ini mungkin tidak lagi berhasil. Juga, ketika beberapa indeks penting tidak ada (khususnya pada kunci asing), kinerja mungkin terganggu dengan menambahkan Include
s, bahkan dengan paket kueri terbaik.
Inti Kerangka Entitas
Ledakan Kartesius
Untuk beberapa alasan, perilaku yang dijelaskan di atas, kueri UNION, ditinggalkan pada EF core 3. Sekarang membuat satu kueri dengan gabungan. Ketika kueri berbentuk "bintang", ini mengarah ke ledakan Cartesian (dalam kumpulan hasil SQL). Saya hanya dapat menemukan catatan yang mengumumkan perubahan yang melanggar ini, tetapi tidak disebutkan alasannya.
Pisahkan kueri
Untuk mengatasi ledakan Cartesian ini, Entity Framework core 5 memperkenalkan konsep kueri terpisah yang memungkinkan memuat data terkait dalam beberapa kueri. Ini mencegah membangun satu set hasil SQL yang besar dan berlipat ganda. Selain itu, karena kompleksitas kueri yang lebih rendah, ini dapat mengurangi waktu yang diperlukan untuk mengambil data bahkan dengan beberapa perjalanan pulang pergi. Namun, ini dapat menyebabkan data yang tidak konsisten saat terjadi pembaruan secara bersamaan.
Beberapa hubungan 1:n keluar dari akar kueri.