Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
Mengkueri secara efisien adalah subjek yang luas, yang mencakup subjek seluas indeks, strategi pemuatan entitas terkait, dan banyak lainnya. Bagian ini merinci beberapa tema umum untuk membuat kueri Anda lebih cepat, dan jebakan yang biasanya ditemui pengguna.
Gunakan indeks dengan benar
Faktor penentu utama dalam apakah kueri berjalan cepat atau tidak adalah apakah kueri akan menggunakan indeks dengan benar jika sesuai: database biasanya digunakan untuk menyimpan data dalam jumlah besar, dan kueri yang melintasi seluruh tabel biasanya merupakan sumber masalah performa serius. Masalah pengindeksan tidak mudah ditemukan, karena tidak segera jelas apakah kueri tertentu akan menggunakan indeks atau tidak. Contohnya:
// 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();
Cara yang baik untuk menemukan masalah pengindeksan adalah dengan terlebih dahulu menentukan kueri yang lambat, lalu memeriksa rencana kuerinya melalui alat favorit database Anda; lihat halaman diagnosis performa untuk informasi selengkapnya tentang cara melakukannya. Rencana kueri menampilkan apakah kueri melintasi seluruh tabel, atau menggunakan indeks.
Sebagai aturan umum, tidak ada pengetahuan EF khusus untuk menggunakan indeks atau mendiagnosis masalah performa yang terkait dengannya; pengetahuan database umum yang terkait dengan indeks sama relevannya dengan aplikasi EF tentang aplikasi yang tidak menggunakan EF. Berikut ini mencantumkan beberapa panduan umum yang perlu diingat saat menggunakan indeks:
- Meskipun indeks mempercepat kueri, indeks juga memperlambat pembaruan karena perlu disimpan up-to-date. Hindari menentukan indeks yang tidak diperlukan, dan pertimbangkan untuk menggunakan filter indeks untuk membatasi indeks ke subset baris, sehingga mengurangi overhead ini.
- Indeks komposit dapat mempercepat kueri yang memfilter beberapa kolom, tetapi mereka juga dapat mempercepat kueri yang tidak memfilter semua kolom indeks - tergantung pada pengurutan. Misalnya, indeks pada kolom A dan B mempercepat pemfilteran kueri oleh A dan B serta kueri yang hanya difilter oleh A, tetapi tidak mempercepat kueri hanya memfilter melalui B.
- Jika kueri difilter menurut ekspresi di atas kolom (misalnya
price / 2
), indeks sederhana tidak dapat digunakan. Namun, Anda dapat menentukan kolom persisten yang disimpan untuk ekspresi Anda, dan membuat indeks di atasnya. Beberapa database juga mendukung indeks ekspresi, yang dapat langsung digunakan untuk mempercepat pemfilteran kueri menurut ekspresi apa pun. - Database yang berbeda memungkinkan indeks dikonfigurasi dengan berbagai cara, dan dalam banyak kasus penyedia EF Core mengeksposnya melalui API Fluent. Misalnya, penyedia SQL Server memungkinkan Anda untuk mengonfigurasi apakah indeks diklusterkan, atau mengatur faktor pengisiannya. Lihat dokumentasi penyedia Anda untuk informasi selengkapnya.
Proyeksikan hanya properti yang Anda butuhkan
EF Core memudahkan untuk mengkueri instans entitas, lalu menggunakan instans tersebut dalam kode. Namun, melakukan kueri pada instans entitas dapat sering menarik kembali jumlah data yang lebih banyak daripada yang diperlukan dari database Anda. Pertimbangkan hal berikut:
await foreach (var blog in context.Blogs.AsAsyncEnumerable())
{
Console.WriteLine("Blog: " + blog.Url);
}
Meskipun kode ini hanya benar-benar membutuhkan setiap properti Blog Url
, seluruh entitas Blog diambil, dan kolom yang tidak diperlukan ditransfer dari database:
SELECT [b].[BlogId], [b].[CreationDate], [b].[Name], [b].[Rating], [b].[Url]
FROM [Blogs] AS [b]
Ini dapat dioptimalkan dengan menggunakan Select
untuk memberi tahu EF kolom mana yang akan diproyeksikan:
await foreach (var blogName in context.Blogs.Select(b => b.Url).AsAsyncEnumerable())
{
Console.WriteLine("Blog: " + blogName);
}
SQL yang dihasilkan hanya menarik kembali kolom yang diperlukan:
SELECT [b].[Url]
FROM [Blogs] AS [b]
Jika Anda perlu memproyeksikan lebih dari satu kolom, proyeksi ke jenis anonim C# dengan properti yang Anda inginkan.
Perhatikan bahwa teknik ini sangat berguna untuk kueri baca-saja, tetapi prosesnya menjadi lebih rumit jika Anda perlu memperbarui blog yang telah diambil, karena pelacakan perubahan EF hanya berfungsi dengan instans entitas. Dimungkinkan untuk melakukan pembaruan tanpa memuat seluruh entitas dengan melampirkan instans Blog yang dimodifikasi dan memberi tahu EF properti mana yang telah berubah, tetapi itu adalah teknik yang lebih canggih yang mungkin tidak sepadan.
Membatasi ukuran resultset
Secara default, kueri mengembalikan semua baris yang cocok dengan filternya:
var blogsAll = await context.Posts
.Where(p => p.Title.StartsWith("A"))
.ToListAsync();
Karena jumlah baris yang dikembalikan tergantung pada data aktual dalam database Anda, tidak mungkin untuk mengetahui berapa banyak data yang akan dimuat dari database, berapa banyak memori yang akan diambil oleh hasil, dan berapa banyak beban tambahan yang akan dihasilkan saat memproses hasil ini (misalnya dengan mengirimkannya ke browser pengguna melalui jaringan). Sangat penting, database pengujian sering berisi sedikit data, sehingga semuanya berfungsi dengan baik saat pengujian, tetapi masalah performa tiba-tiba muncul ketika kueri mulai berjalan pada data dunia nyata dan banyak baris dikembalikan.
Akibatnya, biasanya perlu mempertimbangkan untuk membatasi jumlah hasil.
var blogs25 = await context.Posts
.Where(p => p.Title.StartsWith("A"))
.Take(25)
.ToListAsync();
Minimal, UI Anda dapat menampilkan pesan yang menunjukkan bahwa lebih banyak baris mungkin ada dalam database (dan memungkinkan pengambilannya dengan cara lain). Solusi lengkap akan menerapkan penomoran halaman, di mana UI Anda hanya menampilkan sejumlah baris pada satu waktu, dan memungkinkan pengguna untuk maju ke halaman berikutnya sesuai kebutuhan; lihat bagian berikutnya untuk detail selengkapnya tentang cara menerapkan ini secara efisien.
Paginasi yang efisien
Pengaturan halaman mengacu pada pengambilan hasil secara bertahap melalui halaman, daripada mengambil semua hasil sekaligus; ini biasanya dilakukan untuk kumpulan hasil yang besar, di mana ditampilkan antarmuka pengguna yang memungkinkan pengguna untuk menavigasi ke halaman hasil berikutnya atau sebelumnya. Cara umum untuk menerapkan penomoran halaman dengan database adalah dengan menggunakan Skip
operator dan Take
(OFFSET
dan LIMIT
di SQL); sementara ini adalah implementasi intuitif, ini juga cukup tidak efisien. Untuk paginasi yang memungkinkan pemindahan satu halaman sekaligus (bukan melompat ke halaman sembarang), pertimbangkan untuk menggunakan keyset pagination sebagai gantinya.
Untuk informasi selengkapnya, lihat halaman dokumentasi tentang penomoran halaman.
Hindari ledakan kartesius saat memuat entitas terkait
Dalam database relasional, semua entitas terkait dimuat dengan menggunakan JOIN dalam satu kueri.
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]
Jika blog umum memiliki beberapa posting terkait, baris untuk posting ini akan menduplikasi informasi blog. Duplikasi ini menyebabkan apa yang disebut masalah "ledakan kartesius". Karena lebih banyak hubungan satu-ke-banyak dimuat, jumlah data duplikat dapat tumbuh dan berdampak buruk pada performa aplikasi Anda.
EF memungkinkan menghindari efek ini melalui penggunaan "kueri terpisah", yang memuat entitas terkait melalui kueri terpisah. Untuk informasi selengkapnya, baca dokumentasi tentang kueri terpisah dan tunggal.
Nota
Implementasi kueri terpisah saat ini menjalankan roundtrip untuk setiap kueri. Kami berencana untuk meningkatkan ini di masa depan, dan menjalankan semua kueri dalam satu perjalanan pulang pergi.
Muat entitas terkait dengan bersemangat jika memungkinkan
Disarankan untuk membaca halaman khusus tentang entitas terkait sebelum melanjutkan dengan bagian ini.
Ketika berhadapan dengan entitas terkait, kita biasanya tahu terlebih dahulu apa yang perlu kita muat: contoh umumnya adalah memuat sekumpulan Blog tertentu, bersama dengan semua Posting mereka. Dalam skenario ini, selalu lebih baik menggunakan eager loading, sehingga EF dapat mengambil semua data yang diperlukan dalam sekali perjalanan. Fitur filtered include juga memungkinkan Anda membatasi entitas terkait mana yang ingin Anda muat, sambil menjaga proses pemuatan tetap cepat dan oleh karena itu dapat dilakukan dalam satu kali perjalanan:
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();
}
Dalam skenario lain, kita mungkin tidak tahu entitas terkait mana yang akan kita butuhkan sebelum kita mendapatkan entitas utamanya. Misalnya, ketika memuat beberapa Blog, kita mungkin perlu berkonsultasi dengan beberapa sumber data lain - mungkin layanan web - untuk mengetahui apakah kita tertarik dengan Posting Blog tersebut. Dalam kasus ini, pemuatan eksplisit atau tertunda dapat digunakan untuk mengambil entitas terkait secara terpisah, dan mengisi navigasi Posting Blog. Perhatikan bahwa karena metode ini tidak bersemangat, metode ini memerlukan perjalanan pulang pergi tambahan ke database, yang merupakan sumber perlambatan; tergantung pada skenario spesifik Anda, mungkin lebih efisien untuk selalu memuat semua Posting, daripada menjalankan roundtrips tambahan dan secara selektif hanya mendapatkan Postingan yang Anda butuhkan.
Waspadalah terhadap teknik lazy loading
Lazy loading sering kali tampak seperti cara yang sangat berguna untuk menulis logika database, karena EF Core secara otomatis memuat entitas terkait dari database saat diakses oleh kode Anda. Ini menghindari pemuatan entitas terkait yang tidak diperlukan (seperti pemuatan eksplisit), dan tampaknya membebaskan programmer dari harus berurusan dengan entitas terkait sama sekali. Namun, pemuatan malas cenderung menyebabkan perjalanan bolak-balik yang tidak perlu, yang dapat memperlambat aplikasi.
Pertimbangkan hal berikut:
foreach (var blog in await context.Blogs.ToListAsync())
{
foreach (var post in blog.Posts)
{
Console.WriteLine($"Blog {blog.Url}, Post: {post.Title}");
}
}
Potongan kode ini yang tampaknya tidak bersalah beriterasi melalui semua blog dan posting mereka, dan mencetak hasilnya. Mengaktifkan log pernyataan EF Core mengungkapkan hal-hal berikut:
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
Apa yang terjadi di sini? Mengapa semua permintaan ini dikirim untuk menjalankan loop sederhana di atas? Dalam lazy loading, postingan Blog hanya dimuat ketika properti postingan diakses; sebagai hasilnya, setiap iterasi pada foreach bagian dalam memicu kueri database tambahan, pada siklus pemrosesannya sendiri. Akibatnya, setelah kueri awal memuat semua blog, kita kemudian memiliki kueri lain per blog, memuat semua postingannya; ini kadang-kadang disebut masalah N+1 , dan dapat menyebabkan masalah performa yang sangat signifikan.
Dengan asumsi kita akan membutuhkan semua posting dari blog, masuk akal untuk menggunakan "eager loading" di sini sebagai gantinya. Kita dapat menggunakan operator Sertakan untuk melakukan pemuatan, tetapi karena kita hanya memerlukan URL Blog (dan kita hanya boleh memuat apa yang diperlukan). Jadi kita akan menggunakan proyeksi sebagai gantinya:
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}");
}
}
Dengan ini, EF Core akan mengambil semua Blog dan Postingan mereka dalam satu kueri. Dalam beberapa kasus, mungkin juga berguna untuk menghindari efek ledakan kartesius dengan menggunakan kueri terpisah.
Peringatan
Karena lazy loading dapat dengan mudah secara tidak sengaja memicu masalah N+1, disarankan untuk menghindarinya. Pemuatan yang bersemangat atau eksplisit membuatnya sangat jelas dalam kode sumber ketika roundtrip database terjadi.
Buffering dan streaming
Buffering mengacu pada memuat semua hasil kueri ke dalam memori, sementara itu, streaming memaksudkan bahwa Entity Framework memberikan satu hasil ke aplikasi pada satu waktu, dan tidak pernah menyimpan seluruh set hasil dalam memori. Pada prinsipnya, persyaratan memori kueri streaming tetap - mereka sama apakah kueri mengembalikan 1 baris atau 1000; kueri buffering, di sisi lain, membutuhkan lebih banyak memori semakin banyak baris yang dihasilkan. Untuk kueri yang menghasilkan hasil besar, ini bisa menjadi faktor performa penting.
Apakah kueri menggunakan buffer atau stream tergantung pada bagaimana hal itu dievaluasi.
// 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
Jika kueri Anda hanya mengembalikan beberapa hasil, maka Anda mungkin tidak perlu khawatir tentang hal ini. Namun, jika kueri Anda mungkin mengembalikan sejumlah besar baris, pertimbangkan opsi untuk menggunakan streaming alih-alih buffering.
Nota
Hindari menggunakan ToList atau ToArray jika Anda berniat menggunakan operator LINQ lain pada hasilnya - ini tidak perlu buffer semua hasil ke dalam memori. Gunakan AsEnumerable sebagai gantinya.
Buffering internal oleh EF
Dalam situasi tertentu, EF akan menyangga hasil secara internal, terlepas dari bagaimana Anda mengevaluasi kueri Anda. Dua kasus di mana ini terjadi adalah:
- Ketika strategi eksekusi yang mencoba ulang sedang diterapkan. Ini dilakukan untuk memastikan hasil yang sama dikembalikan jika kueri dicoba kembali nanti.
- Saat kueri terpisah digunakan, hasil dari semua kueri kecuali yang terakhir disimpan dalam buffer - kecuali MARS (Beberapa Set Hasil Aktif) diaktifkan di SQL Server. Ini karena biasanya tidak mungkin memiliki beberapa hasil kueri yang aktif secara bersamaan.
Perhatikan bahwa buffering internal ini terjadi selain buffering apa pun yang Anda sebabkan melalui operator LINQ. Misalnya, jika Anda menggunakan ToList pada kueri dan strategi eksekusi ulang diterapkan, hasilnya dimuat ke dalam memori dua kali: sekali secara internal oleh EF, dan sekali oleh ToList.
Pelacakan, non-pelacakan, dan penyelesaian identitas
Disarankan untuk membaca halaman khusus tentang pelacakan dan tanpa pelacakan sebelum melanjutkan dengan bagian ini.
EF melacak instans entitas secara default, sehingga perubahan pada instans tersebut terdeteksi dan bertahan saat SaveChanges dipanggil. Dampak lain dari pelacakan kueri adalah EF mendeteksi apakah instance telah dimuat untuk data Anda, dan akan secara otomatis mengembalikan instance yang terlacak tersebut daripada mengembalikan yang baru; ini disebut resolusi identitas. Dari perspektif performa, pelacakan perubahan berarti sebagai berikut:
- EF secara internal mempertahankan daftar instans yang terlacak. Saat data baru dimuat, EF memeriksa kamus untuk melihat apakah instans sudah dilacak untuk kunci entitas tersebut (resolusi identitas). Pemeliharaan kamus dan pencarian membutuhkan waktu saat memuat hasil kueri.
- Sebelum menyerahkan instans yang dimuat ke aplikasi, EF membuat snapshot dari instans tersebut dan menyimpan snapshot secara internal. Ketika SaveChanges dipanggil, instans aplikasi dibandingkan dengan rekam jepret untuk menemukan perubahan yang akan dipertahankan. Rekam jepret membutuhkan lebih banyak memori, dan proses rekam jepret itu sendiri membutuhkan waktu; terkadang dimungkinkan untuk menentukan perilaku rekam jepret yang berbeda, mungkin lebih efisien melalui pembanding nilai, atau menggunakan proksi pelacakan perubahan untuk melewati proses rekam jepret sama sekali (meskipun itu datang dengan serangkaian kekurangannya sendiri).
Dalam situasi hanya-baca di mana perubahan tidak disimpan kembali ke database, beban tambahan tersebut dapat dihindari dengan menggunakan kueri tanpa pelacakan. Namun, karena kueri tanpa pelacakan tidak melakukan resolusi identitas, baris database yang dirujuk oleh beberapa baris lain yang dimuat akan dimaterialisasikan dalam bentuk instans yang berbeda.
Untuk mengilustrasikan, asumsikan kita memuat sejumlah besar Posting dari database, serta Blog yang dirujuk oleh setiap Postingan. Jika kebetulan 100 Postingan mengacu pada Blog yang sama, sebuah kueri pelacakan akan mendeteksi ini melalui pengenalan identitas, dan semua instans Post yang ada akan merujuk pada instans Blog yang sudah digabung. Kueri tanpa pelacakan, sebaliknya, menduplikasi Blog yang sama 100 kali - dan kode aplikasi harus ditulis dengan sesuai.
Berikut adalah hasil untuk tolok ukur membandingkan perilaku pelacakan vs. tanpa pelacakan untuk kueri yang memuat 10 Blog dengan 20 Posting masing-masing. Kode sumber tersedia di sini, jangan ragu untuk menggunakannya sebagai dasar untuk pengukuran Anda sendiri.
Metode | NumBlogs | JumlahPosPerBlog | Berarti | Kesalahan | StdDev | Tengah | Nisbah | RasioSD | Gen 0 | Gen 1 | Gen 2 | Dialokasikan |
---|---|---|---|---|---|---|---|---|---|---|---|---|
AsTracking | 10 | 20 | 1,414.7 kami | 27.20 kami | 45.44 kami | 1.405,5 kami | 1,00 | 0.00 | 60.5469 | 13.6719 | - | 380,11 KB |
AsNoTracking | 10 | 20 | 993.3 kami | 24.04 kami | 65.40 kami | 966.2 kami | 0.71 | 0,05 | 37.1094 | 6.8359 | - | 232,89 KB |
Akhirnya, dimungkinkan untuk melakukan pembaruan tanpa overhead pelacakan perubahan, dengan menggunakan kueri tanpa pelacakan dan kemudian melampirkan instans yang dikembalikan ke konteks, menentukan perubahan mana yang akan dilakukan. Ini memindahkan beban pelacakan perubahan dari EF kepada pengguna, dan hanya boleh dilakukan jika beban pelacakan perubahan telah terbukti tidak dapat diterima melalui analisis kinerja atau tolok ukur.
Menggunakan kueri SQL
Dalam beberapa kasus, SQL yang lebih dioptimalkan ada untuk kueri Anda, yang tidak dihasilkan EF. Ini dapat terjadi ketika konstruksi SQL adalah ekstensi khusus untuk database Anda yang tidak didukung, atau hanya karena EF belum menerjemahkannya. Dalam kasus ini, menulis SQL dengan tangan dapat memberikan peningkatan performa yang substansial, dan EF mendukung beberapa cara untuk melakukan ini.
- Gunakan kueri SQL langsung dalam kueri Anda, misalnya melalui FromSqlRaw. EF bahkan memungkinkan Anda menyusun di atas SQL menggunakan kueri LINQ reguler, memungkinkan Anda untuk menyatakan hanya sebagian kueri di SQL. Ini adalah teknik yang baik ketika SQL hanya perlu digunakan dalam satu kueri di basis kode Anda.
- Tentukan fungsi yang ditentukan pengguna (UDF), lalu panggil dari kueri Anda. Perhatikan bahwa EF memungkinkan UDF untuk mengembalikan set hasil lengkap - ini dikenal sebagai fungsi bernilai tabel (TVF) - dan juga memungkinkan pemetaan
DbSet
ke fungsi, sehingga dapat terlihat persis seperti tabel lainnya. - Tentukan tampilan database dan kueri darinya dalam kueri Anda. Perhatikan bahwa tidak seperti fungsi, tampilan tidak dapat menerima parameter.
Nota
SQL mentah umumnya harus digunakan sebagai upaya terakhir, setelah memastikan bahwa EF tidak dapat menghasilkan SQL yang Anda inginkan, dan ketika performa cukup penting bagi kueri yang diberikan untuk membenarkannya. Menggunakan SQL mentah membawa kerugian pemeliharaan yang cukup besar.
Pemrograman asinkron
Sebagai aturan umum, agar aplikasi Anda dapat diskalakan, penting untuk selalu menggunakan API asinkron daripada yang sinkron (misalnya SaveChangesAsync daripada SaveChanges). API sinkronisasi memblokir utas selama proses I/O database, meningkatkan kebutuhan akan utas dan jumlah sakelar konteks utas yang terjadi.
Untuk informasi selengkapnya, lihat halaman tentang pemrograman asinkron.
Peringatan
Hindari mencampur kode sinkron dan asinkron dalam aplikasi yang sama - sangat mudah untuk secara tidak sengaja memicu masalah kelaparan kumpulan utas yang halus.
Peringatan
Implementasi asinkron Microsoft.Data.SqlClient sayangnya memiliki beberapa masalah yang diketahui (misalnya #593, #601, dan lainnya). Jika Anda melihat masalah performa yang tidak terduga, coba gunakan eksekusi perintah sinkronisasi, terutama saat berhadapan dengan teks besar atau nilai biner.
Sumber daya tambahan
- Lihat halaman topik performa tingkat lanjut untuk topik tambahan yang terkait dengan kueri yang efisien.
- Lihat bagian performa halaman dokumentasi perbandingan null untuk beberapa praktik terbaik saat membandingkan nilai nullable.