Jegyzet
Az oldalhoz való hozzáférés engedélyezést igényel. Próbálhatod be jelentkezni vagy könyvtárat váltani.
Az oldalhoz való hozzáférés engedélyezést igényel. Megpróbálhatod a könyvtár váltását.
Teljesítményproblémák önálló lekérdezésekkel
A relációs adatbázisok használatakor az EF betölti a kapcsolódó entitásokat a JOIN-ek egyetlen lekérdezésbe való bevezetésével. Bár a JOIN-k meglehetősen szabványosak az SQL használatakor, jelentős teljesítményproblémákat okozhatnak, ha helytelenül használják őket. Ez a lap ismerteti ezeket a teljesítményproblémákat, és egy alternatív módszert mutat be a körülöttük működő kapcsolódó entitások betöltésére.
Cartesian robbanás
Vizsgáljuk meg a következő LINQ-lekérdezést és annak lefordított SQL-ekvivalensét:
var blogs = await ctx.Blogs
.Include(b => b.Posts)
.Include(b => b.Contributors)
.ToListAsync();
SELECT [b].[Id], [b].[Name], [p].[Id], [p].[BlogId], [p].[Title], [c].[Id], [c].[BlogId], [c].[FirstName], [c].[LastName]
FROM [Blogs] AS [b]
LEFT JOIN [Posts] AS [p] ON [b].[Id] = [p].[BlogId]
LEFT JOIN [Contributors] AS [c] ON [b].[Id] = [c].[BlogId]
ORDER BY [b].[Id], [p].[Id]
Ebben a példában, mivel mind a Posts, mind a Contributors elemei egy Blog gyűjtemény navigációi – és azonos szinten vannak –, a relációs adatbázisok egy keresztterméket adnak vissza: minden Posts sor össze van kapcsolva minden Contributors sorral. Ez azt jelenti, hogy ha egy adott blog 10 bejegyzéssel és 10 közreműködővel rendelkezik, az adatbázis 100 sort ad vissza az adott bloghoz. Ez a jelenség - más néven cartesian robbanás - hatalmas mennyiségű adat akaratlan átvitelét okozhatja az ügyfélhez, különösen, ha a lekérdezéshez több további JOIN kapcsolatot adnak; ez az adatbázis-alkalmazások egyik fő teljesítményproblémája lehet.
Vegye figyelembe, hogy a cartesian robbanás nem fordul elő, ha a két JOIN nem azonos szinten van:
var blogs = await ctx.Blogs
.Include(b => b.Posts)
.ThenInclude(p => p.Comments)
.ToListAsync();
SELECT [b].[Id], [b].[Name], [t].[Id], [t].[BlogId], [t].[Title], [t].[Id0], [t].[Content], [t].[PostId]
FROM [Blogs] AS [b]
LEFT JOIN [Posts] AS [p] ON [b].[Id] = [p].[BlogId]
LEFT JOIN [Comment] AS [c] ON [p].[Id] = [c].[PostId]
ORDER BY [b].[Id], [t].[Id]
Ebben a lekérdezésben a Comments a Post gyűjtemény navigációja, ellentétben az előző lekérdezés Contributors-jével, amely a Blog gyűjtemény navigációja volt. Ebben az esetben egyetlen sor lesz visszaadva minden olyan megjegyzéshez, amelyet egy blog (a bejegyzései révén) tartalmaz, és a kereszttermék nem fordul elő.
Adatok duplikálása
A JOIN-k létrehozhatnak egy másik típusú teljesítményproblémát. Vizsgáljuk meg a következő lekérdezést, amely csak egyetlen gyűjteménynavigációt tölt be:
var blogs = await ctx.Blogs
.Include(b => b.Posts)
.ToListAsync();
SELECT [b].[Id], [b].[Name], [b].[HugeColumn], [p].[Id], [p].[BlogId], [p].[Title]
FROM [Blogs] AS [b]
LEFT JOIN [Posts] AS [p] ON [b].[Id] = [p].[BlogId]
ORDER BY [b].[Id]
A tervezett oszlopok vizsgálatakor a lekérdezés által visszaadott sorok mind a Blogs és Posts táblák tulajdonságait tartalmazzák. Ez azt jelenti, hogy a blogtulajdonságok duplikálva vannak minden olyan bejegyzéshez, amelyet a blog tartalmaz. Bár ez általában normális, és nem okoz problémát, ha a Blogs táblázat nagyon nagy oszlopot (például bináris adatokat vagy hatalmas szöveget) tartalmaz, az oszlop duplikálva lesz, és többször is vissza lesz küldve az ügyfélnek. Ez jelentősen növelheti a hálózati forgalmat, és hátrányosan befolyásolhatja az alkalmazás teljesítményét.
Ha valójában nincs szüksége a hatalmas oszlopra, könnyen ne kérdezze le.
var blogs = await ctx.Blogs
.Select(b => new
{
b.Id,
b.Name,
b.Posts
})
.ToListAsync();
Ha egy kivetítés használatával explicit módon választja ki a kívánt oszlopokat, kihagyhatja a nagy oszlopokat, és javíthatja a teljesítményt; vegye figyelembe, hogy ez az adatkettőzéstől függetlenül jó ötlet, ezért érdemes akkor is elvégezni, ha nem tölt be gyűjteménynavigációt. Mivel azonban ezzel a leképezéssel a blog egy névtelen típusba kerül, az EF nem követi az eredeti blogot, így a módosítások nem menthetők vissza szokásos módon.
Érdemes megjegyezni, hogy a kartéziánus robbanással ellentétben a JOIN-ok által okozott adatkettőzések általában nem jelentősek, mivel a duplikált adatméret elhanyagolható; ez általában csak egy probléma, ami miatt csak akkor kell aggódnia, ha nagy oszlopokat tartalmaz a főtáblában.
Lekérdezések felosztása
A fent ismertetett teljesítményproblémák megkerüléséhez az EF lehetővé teszi, hogy egy adott LINQ-lekérdezés több SQL-lekérdezésre legyen felosztva . A JOIN-ok helyett az osztott lekérdezések további SQL-lekérdezéseket hoznak létre az egyes beépített gyűjteménynavigációkhoz:
using (var context = new BloggingContext())
{
var blogs = await context.Blogs
.Include(blog => blog.Posts)
.AsSplitQuery()
.ToListAsync();
}
A következő SQL-t hozza létre:
SELECT [b].[BlogId], [b].[OwnerId], [b].[Rating], [b].[Url]
FROM [Blogs] AS [b]
ORDER BY [b].[BlogId]
SELECT [p].[PostId], [p].[AuthorId], [p].[BlogId], [p].[Content], [p].[Rating], [p].[Title], [b].[BlogId]
FROM [Blogs] AS [b]
INNER JOIN [Posts] AS [p] ON [b].[BlogId] = [p].[BlogId]
ORDER BY [b].[BlogId]
Figyelmeztetés
Ha 10 előtt használ felosztott lekérdezéseket a Skip/Take ef verzióval, különös figyelmet kell fordítani arra, hogy a lekérdezések sorrendje teljesen egyedi legyen; ha nem így tesz, helytelen adatokat ad vissza. Ha például az eredmények csak dátum szerint vannak rendezve, de több találat is lehet ugyanazzal a dátummal, akkor a felosztott lekérdezések mindegyike különböző eredményeket kaphat az adatbázisból. A dátum és az azonosító (vagy bármely más egyedi tulajdonság vagy tulajdonságok kombinációja) alapján történő megrendelés teljesen egyedivé teszi a rendelést, és elkerüli ezt a problémát. Vegye figyelembe, hogy a relációs adatbázisok alapértelmezés szerint nem alkalmaznak sorrendet, még az elsődleges kulcson sem.
Megjegyzés
Az egy-az-egyhez kapcsolódó entitások mindig JOIN-eken keresztül töltődnek be ugyanabban a lekérdezésben, mivel nincs hatással a teljesítményre.
Osztott lekérdezések engedélyezése globálisan
Az osztott lekérdezéseket az alkalmazás környezetének alapértelmezett értékeként is konfigurálhatja:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseSqlServer(
@"Server=(localdb)\mssqllocaldb;Database=EFQuerying;Trusted_Connection=True;ConnectRetryCount=0",
o => o.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery));
}
Ha az osztott lekérdezések alapértelmezettként vannak konfigurálva, akkor is konfigurálható, hogy egyes lekérdezések egyetlen lekérdezésként legyenek végrehajtva:
using (var context = new SplitQueriesBloggingContext())
{
var blogs = await context.Blogs
.Include(blog => blog.Posts)
.AsSingleQuery()
.ToListAsync();
}
Az EF Core alapértelmezés szerint egyetlen lekérdezési módot használ konfiguráció hiányában. Mivel teljesítményproblémákat okozhat, az EF Core figyelmeztetést generál, amikor a következő feltételek teljesülnek:
- Az EF Core azt észleli, hogy a lekérdezés több gyűjteményt tölt be.
- A felhasználó globálisan nem konfigurálta a lekérdezésfelosztási módot.
- A felhasználó nem használt
AsSingleQuery/AsSplitQueryoperátort a lekérdezésben.
A figyelmeztetés kikapcsolásához konfigurálja a lekérdezés felosztási módját globálisan vagy lekérdezési szinten egy megfelelő értékre.
Az osztott lekérdezések jellemzői
Bár az osztott lekérdezés elkerüli a JOIN-ekkel és a cartesian robbanással kapcsolatos teljesítményproblémákat, hátrányai is vannak:
- Bár a legtöbb adatbázis garantálja az egyetlen lekérdezés adatkonzisztenciáját, több lekérdezésre nem létezik ilyen garancia. Ha az adatbázis a lekérdezések végrehajtásakor egyidejűleg frissül, előfordulhat, hogy az eredményül kapott adatok nem lesznek konzisztensek. Ezt úgy háríthatja el, hogy a lekérdezéseket szerializálható vagy pillanatkép-tranzakcióba csomagolja, bár ez önmagában is teljesítményproblémákat okozhat. További információkért tekintse meg az adatbázis dokumentációját.
- Az egyes lekérdezések jelenleg további hálózati körutat igényelnek az adatbázishoz. Több hálózati körutazás csökkentheti a teljesítményt, különösen akkor, ha az adatbázishoz való késleltetés magas (például felhőszolgáltatások esetén).
- Bár egyes adatbázisok lehetővé teszik több lekérdezés eredményeinek egyidejű használatát (SQL Server a MARS-jal, Sqlite), a legtöbb esetben csak egyetlen lekérdezés lehet aktív egy adott időpontban. Ezért a korábbi lekérdezések összes eredményét pufferelni kell az alkalmazás memóriájában a későbbi lekérdezések végrehajtása előtt, ami megnövekedett memóriaigényhez vezet.
- Hivatkozási navigációk és gyűjteménynavigációk hozzáadásakor a felosztott lekérdezések mindegyike összekapcsolja a referencianavigációkat. Ez csökkentheti a teljesítményt, különösen akkor, ha sok hivatkozási navigáció van. Kérjük, szavazzon a #29182 javítására, ha szeretné, hogy ezt kijavítsák.
Sajnos nincs olyan stratégia, amely minden forgatókönyvnek megfelelő kapcsolódó entitásokat töltene be. Az önálló és felosztott lekérdezések előnyeit és hátrányait gondosan mérlegelve válassza ki az igényeinek megfelelőt.