Sissejuhatus
Uues kaalus mÔtleme erinevatele optimeerimise meetoditele LINQ-pÀringud.
Siin on veel mÔned koodide optimeerimise lÀhenemised, mis on seotud LINQ-pÀringutega.
On teada, et LINQ(Language-Integrated Query) on lihtne ja mugav keelepĂ€ringute sĂŒsteem andmeallikatele.
A LINQ to SQL on andmebaasidele juurdepÀÀsu tehnoloogia. See on vÔimas tööriist andmetega töötamiseks, kus deklaratiivse keele kaudu konstrueeritakse pÀringud, mis seejÀrel muudetakse SQL-pÀringuid platvormi poolt ja saadetakse andmebaasi serverisse tÀitmiseks. Meie kontekstis mÔistame andmebaasina MS SQL Server.
Kuid LINQ-pĂ€ringud ei muutu optimaalselt kirjutatuks SQL-pĂ€ringuid, mida oskab kirjutada kogenud DBA kĂ”igi optimeerimise nĂŒanssidega SQL-pĂ€ringud:
- optimaalsed ĂŒhendused (JOIN) ja tulemuste filtreerimine (KUS)
- palju nĂŒansse ĂŒhenduste ja grupi tingimuste kasutamisel
- palju variatsioone tingimuste asendamisel IN . Tundub, et EXISTSja NOT IN, EXISTS
- vahepealne tulemuste vahemÀlu ajutiste tabelite, CTE-de, tabelite muutujate kaudu
- kasutades lauseid (OPTION) juhiste ja tabeli vihjete mÀÀrangutega WITH (âŠ)
- kasutades indekseeritud vaateid, kui ĂŒht vahendit liigsete andmelugemiste vĂ€ltimiseks valimite korral
Peamised kitsaskohad tekkivatel SQL-pÀringud kompileerimise kÀigus LINQ-pÀringud on:
- konsolideerimine kogu andmete valimise mehhanismi ĂŒhte pĂ€ringusse
- identsete koodiblokkide dubleerimine, mis toob lÔpuks kaasa mitmekordseid liigseid andmelugemisi
- komplekssete tingimuste rĂŒhmad (loogilised âjaâ ja âvĂ”iâ) â JA ja OR, keerukatesse tingimustesse ĂŒhendades toob see kaasa, et optimeerija, omades sobivaid mitteklastrilisi indekseid vajalikel vĂ€liadel, hakkab lĂ”ppkokkuvĂ”ttes ikkagi klastrisse indeksi skaneerimist tegema (INDEX SCAN) tingimusgruppide jĂ€rgi
- sĂŒgav alampĂ€ringute pesitsus muudab SQL-kĂ€skude ja pĂ€ringute plaani analĂŒĂŒsi arendajate poolt vĂ€ga probleemseks Optimeerimise meetodid DBA
Liigume nĂŒĂŒd otse optimeerimise meetoditesse.
1) TĂ€iendav indekseerimine
Parim on kaaluda filtreid peamistel valimistabelitel, kuna vĂ€ga sageli ehitatakse kogu pĂ€ring ĂŒhe vĂ”i kahe peamise tabeli (taotlused-inimesed-operatsioonid) ĂŒmber ja tavalise tingimuste kogumiga (IsClosed, Canceled, Enabled, Status). Oluline on luua vastavad indeksid tuvastatud valimitele.
Parima on filtreid vaadelda pĂ”hivĂ€ljundite tabelites, kuna tihti ehitatakse kogu pĂ€ring ĂŒhele vĂ”i kahele pĂ”hiteabele (taotlused-isikud-tegevused) ja tavalise tingimuste kogumiga (IsClosed, Canceled, Enabled, Status). Oluline on luua vastavad indeksid leitud vĂ€ljavĂ”tete jaoks.
See raisi on lahendusel mÔte, kui valik nende vÀljade jÀrgi piirab oluliselt tagastatavaid andmeid.
NÀiteks, meil on 500000 taotlust. Siiski, aktiivseid taotlusi on ainult 2000 kirjet. Siis Ôigesti valitud indeks pÀÀstab meid INDEX SCAN suurustabelist ning vÔimaldab kiirelt andmeid lÀbi mitteklasterdatud indeksi valida.
Samuti saab indeksite puudumise tuvastada pĂ€ringute plaanide analĂŒĂŒsi vihjete vĂ”i sĂŒsteemsete vaadete statistika kogumise kaudu MS SQL Server:
KÔik vaateandmed sisaldavad teavet puuduvate indeksite kohta, vÀlja arvatud ruumilised indeksid.
Kuid indeksid ja vahemÀlu on sageli meetodid, millega vÔidelda halvasti kirjutatud LINQ-pÀringud ja SQL-pÀringud.
Karm praktika nĂ€itab, et Ă€ri jaoks on sageli oluline Ă€riomaduste rakendamine kindlate tĂ€htaegadega. SeetĂ”ttu tĂ”lgitakse sageli keerulised pĂ€ringud taustsĂŒsteemi koos vahemĂ€luga.
Osaliselt on see Ôigustatud, kuna kasutaja ei vaja alati kÔige vÀrskemaid andmeid ning kasutajaliidese vastamise tase on vastuvÔetav.
See lĂ€henemine vĂ”imaldab lahendada Ă€ri pĂ€ringud, kuid alandab lĂ”puks infotehnoloogilise sĂŒsteemi tĂ”husust, lihtsalt probleemide lahendamise edasi lĂŒkates.
Samuti tuleks meeles pidada, et uute indeksite lisamiseks vajalike leidmine, ettepanekud MS SQL optimeerimise osas vÔivad olla ebatÀpsed, sealhulgas jÀrgmistes tingimustes:
- kui juba eksisteerivad sarnaste vÀljadega indeksid
- kui vÀljad tabelis ei saa indeksit moodustada indeksimise piirangute tÔttu (tÀiendav teave on kirjeldatud ).
2) Atributide ĂŒhendamine uue atribuudina
MÔnikord saab mÔningaid tingimuste grupina toimivaid vÀlju asendada uue vÀlja loomisega.
See on eriti aktuaalne olekuvÀljade puhul, mis on tavaliselt kas bitilised vÔi tÀisarvulised.
NĂ€ide:
IsClosed = 0 JA Canceled = 0 JA Enabled = 0 asendatakse Status = 1.
Siin tutvustatakse tÀisarvulist atribuuti Status, mis tagatakse nende staatuste tÀitmisega tabelis. Edasi toimub selle uue atribuudi indekseerimine.
See on fundamentaalne lahendus jĂ”udlusprobleemile, kuna me kĂŒsime andmeid ilma liigsete arvutusteta.
3) Vaate materiaaliseerimine
Kahjuks ei LINQ pÀringutes ei saa otse kasutada ajutisi tabeleid, CTE-d ja tabelimuutujad.
Siiski on olemas veel ĂŒks optimeerimise viis selle juhtumi jaoks â indeksit pĂ”hinevad vaated.
Klauslite rĂŒhm (ĂŒlevalt toodud nĂ€ites) IsClosed = 0 JA Canceled = 0 JA Enabled = 0 (vĂ”i kogum teisi sarnaseid klausi) saab hĂ€sti kasutada indeksit pĂ”hinevas vaates, hoidudes vĂ€ikese andmeosa suurtest kogustest.
Kuid vaate materialiseerimisel on mitmeid piiranguid:
- ala pÀringute kasutamine, klausid EXISTS peavad olema asendatud kasutamisega JOIN
- ei saa kasutada klausi kogumi operaator, toetavad, UNION ALL, ERAND, TABLESAMPLE
- ei saa kasutada tabelihindeid ega klausi OPTION
- pole vĂ”imalust töötada tsĂŒklitega
- pole vĂ”imalik andmeid ĂŒhes vaates erinevatest tabelitest vĂ€ljendada
Oluline on meeles pidada, et indeksit pÔhinevast vaate kasutamise tegelik kasu saab tegelikult ainult siis, kui see on indekseeritud.
Kuid vaate nimetamisel ei pruugi neid indekseid kasutatud olla, ja nende selgeks kasutamiseks tuleb mÀrkida WITH (NOEXPAND).
mrkaran LINQ pĂ€ringutes ei saa mÀÀrata tabelihindeid, nii et tuleb teha veel ĂŒks vaade â âĂŒmbrisâ jĂ€rgmises vormis:
CREATE VIEW NIME_VAATAMINE AS SELECT * FROM MAT_VIEW WITH (NOEXPAND);
4) Tabelifunktsioonide kasutamine
Tihti on LINQ pÀringutes suured ala pÀringute plokid vÔi plokid, mis kasutavad keerulise struktuuriga vaateid, lÔpuks moodustavad pÀringu, millel on vÀga keeruline ja ebaefektiivne tÀitmistruktuur.
Tabelifunktsioonide kasutamise peamised eelised LINQ pÀringutes:
- VÔimalus, nagu ka vaadete puhul, kasutada ja mÀrgata kui objekti, kuid saab edastada sisendparameetrite kogumi:
FROM FUNCTION(@param1, @param2 âŠ)
lÔpuks on vÔimalik saavutada andmete paindlik valik - Tabelifunktsiooni kasutamisel pole selliseid rangeid piiranguid nagu eespool kirjeldatud indeksit pÔhinevates vaadetes:
- Tabelihinded:
lÀbi LINQ ei saa mÀrkida, milliseid indekseid peab kasutama ja mÀÀrata andmete isolatsioonitaset pÀringus.
Kuid funktsioonis on need vÔimalused olemas.
Funktsiooni abil on vĂ”imalik saavutada piisavalt pĂŒsiv pĂ€ringute tĂ€itmisplaan, kus on mÀÀratletud reeglid indeksite jaoks ja andmete isolatsioonitasemed - Funktsiooni kasutamine vĂ”imaldab vĂ”rreldes indeksit pĂ”hinevate vaadetega saada:
- keerulise andmevaliku loogika (sealhulgas tsĂŒklite kasutamine)
- andmete valimine erinevatest tabelitest
- omandi resize. kogumi operaator, toetavad ja EXISTS
- Tabelihinded:
- Pakkumine OPTION on vÀga kasulik, kui peame tagama paralleelsuse juhtimise OPTION(MAXDOP N), pÀringu tÀitmisplaani jÀrjekord. NÀiteks:
- vÔib mÀÀrata sunniviisilise pÀringu plaani loomise OPTION (RECOMPILE)
- vĂ”ib mÀÀrata vajaduse tagada pĂ€ringu plaanile sunduslik kasutamine ĂŒhendusjĂ€rjekorrast, mis on mÀÀratletud pĂ€ringus OPTION (FORCE ORDER)
Detailsemalt OPTION kirjeldatud .
- Kasutades kÔige kitsamat ja vajaliku andmeosa:
Ei ole vaja hoida suuri andmekogumeid cache'ides (nagu indekseeritud vaadetega), millest veel tuleks filtri jÀrgi andmeid valida.
NÀiteks on tabel, mille filtreerimiseks KUS kasutatakse kolme vÀlja (a, b, c).Eeldatakse, et kÔikidel pÀringutel on konstantne tingimus a = 0 ja b = 0.
Siiski on pÀring vÀljale c rohkem varieeruv.
Olgu tingimus a = 0 ja b = 0 tÔeliselt abiks, et piirata vajaliku tulemuse kogumit tuhandete rekordite juurde, kuid tingimus jot piirab valiku saja rekordiga.
Siin vÔib tabelifunktsioon osutuda kasulikumaks valikuks.
Lisaks on tabelifunktsioon ennustatav ja stabiilne tÀitmise ajas.
NĂ€ited
Vaatame rakenduse nÀidet andmebaasis Questions.
On pĂ€ring SELECT, mis ĂŒhendab mitmeid tabeleid ja kasutab ĂŒhte vaadet (OperativeQuestions), kus kontrollitakse e-posti kaudu seotust (kasutades EXISTS) "Aktiivsete pĂ€ringute" ([OperativeQuestions]):
PĂ€ring nr 1
(@p__linq__0 nvarchar(4000))SELECT
1 AS [C1],
[Extent1].[Id] AS [Id],
[Join2].[Object_Id] AS [Object_Id],
[Join2].[ObjectType_Id] AS [ObjectType_Id],
[Join2].[Name] AS [Name],
[Join2].[ExternalId] AS [ExternalId]
FROM [dbo].[Questions] AS [Extent1]
INNER JOIN (SELECT [Extent2].[Object_Id] AS [Object_Id],
[Extent2].[Question_Id] AS [Question_Id], [Extent3].[ExternalId] AS [ExternalId],
[Extent3].[ObjectType_Id] AS [ObjectType_Id], [Extent4].[Name] AS [Name]
FROM [dbo].[ObjectQuestions] AS [Extent2]
INNER JOIN [dbo].[Objects] AS [Extent3] ON [Extent2].[Object_Id] = [Extent3].[Id]
LEFT OUTER JOIN [dbo].[ObjectTypes] AS [Extent4]
ON [Extent3].[ObjectType_Id] = [Extent4].[Id] ) AS [Join2]
ON [Extent1].[Id] = [Join2].[Question_Id]
WHERE ([Extent1].[AnswerId] IS NULL) AND (0 = [Extent1].[Exp]) AND ( EXISTS (SELECT
1 AS [C1]
FROM [dbo].[OperativeQuestions] AS [Extent5]
WHERE (([Extent5].[Email] = @p__linq__0) OR (([Extent5].[Email] IS NULL)
AND (@p__linq__0 IS NULL))) AND ([Extent5].[Id] = [Extent1].[Id])
));
Vaade omab ĂŒsna keerulist struktuuri: seal on alampĂ€ringute ĂŒhendusi ja kasutamine sorteerimist STDEV, mis on ĂŒldiselt piisavalt ressursimahukas operatsioon.
Valik OperativeQuestions'i umbes kĂŒmne tuhande kirje ulatuses.
Selle pÀringu peamine probleem on see, et vÀlisest pÀringust saadud kirjeid piiratakse sisemise alampÀringuga vaates [OperativeQuestions], mis peab meie vÀljundit piirama [Email] = @p__linq__0 (lÀbi EXISTS) sadade kirjeteni.
Ja vĂ”ib tunduda, et alampĂ€ring peaks ĂŒhel korral arvestama kirjeid [Email] = @p__linq__0, ning siis peaks need paar sada kirjet ĂŒhendama Id jĂ€rgi Questions-iga, ja pĂ€ring oleks kiire.
Tegelikult toimub kĂ”igi tabelite jĂ€rjestikune ĂŒhendamine: nii Id Questions'ite vastavuse kontrollimine Id-dega OperativeQuestions'is, kui ka filtreerimine Email'i jĂ€rgi.
PĂ”himĂ”tteliselt töötab pĂ€ring kĂ”igi kĂŒmnete tuhandete kirje ĂŒle OperativeQuestions'is, kuigi vajalikud on ainult huvitatud andmed Email'i kohta.
OperativeQuestions'i vaate tekst:
PĂ€ring nr 2
CREATED VIEW [dbo].[OperativeQuestions]
AS
SELECT DISTINCT Q.Id, USR.email AS Email
FROM [dbo].Questions AS Q INNER JOIN
[dbo].ProcessUserAccesses AS BPU ON BPU.ProcessId = CQ.Process_Id
OUTER APPLY
(SELECT 1 AS HasNoObjects
WHERE NOT EXISTS
(SELECT 1
FROM [dbo].ObjectUserAccesses AS BOU
WHERE BOU.ProcessUserAccessId = BPU.[Id] AND BOU.[To] IS NULL)
) AS BO INNER JOIN
[dbo].Users AS USR ON USR.Id = BPU.UserId
WHERE CQ.[Exp] = 0 AND CQ.AnswerId IS NULL AND BPU.[To] IS NULL
AND (BO.HasNoObjects = 1 OR
EXISTS (SELECT 1
FROM [dbo].ObjectUserAccesses AS BOU INNER JOIN
[dbo].ObjectQuestions AS QBO
ON QBO.[Object_Id] =BOU.ObjectId
WHERE BOU.ProcessUserAccessId = BPU.Id
AND BOU.[To] IS NULL AND QBO.Question_Id = CQ.Id));
Algne vaate kaardistus DbContextis (EF Core 2)
public class QuestionsDbContext : DbContext
{
//...
public DbQuery<OperativeQuestion> OperativeQuestions { get; set; }
//...
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Query<OperativeQuestion>().ToView("OperativeQuestions");
}
}
Algne LINQ pÀring
var businessObjectsData = await context
.OperativeQuestions
.Where(x => x.Email == Email)
.Include(x => x.Question)
.Select(x => x.Question)
.SelectMany(x => x.ObjectQuestions,
(x, bo) => new
{
Id = x.Id,
ObjectId = bo.Object.Id,
ObjectTypeId = bo.Object.ObjectType.Id,
ObjectTypeName = bo.Object.ObjectType.Name,
ObjectExternalId = bo.Object.ExternalId
})
.ToListAsync();
Antud konkreetses olukorras kaalutakse selle probleemi lahendamist ilma infrastruktuurimuudatusteta, ilma eraldi tabeli loomata valmis tulemustega («Aktiivsed pÀringud»), mille jaoks oleks vajalik mehhanism selle andmete tÀitmiseks ja ajakohasena hoidmiseks.
Kuigi see on hea lahendus, on olemas ka teine vĂ”imalus selle ĂŒlesande optimeerimiseks.
Peamine eesmÀrk on salvestada kirjed [Email] = @p__linq__0 vaates OperativeQuestions.
Introduseeritakse tabelifunktsioon [dbo].[OperativeQuestionsUserMail] andmebaasi.
Saates sisendparameetrina Email, saame tagasi vÀÀrtustetabeli:
PĂ€ring nr 3
CREATE FUNCTION [dbo].[OperativeQuestionsUserMail]
(
@Email nvarchar(4000)
)
RETURNS
@tbl TABLE
(
[Id] uniqueidentifier,
[Email] nvarchar(4000)
)
AS
BEGIN
INSERT INTO @tbl ([Id], [Email])
SELECT Id, @Email
FROM [OperativeQuestions] AS [x] WHERE [x].[Email] = @Email;
RETURN;
END
Siin tagastatakse vÀÀrtustetabel eelnevalt mÀÀratletud andmestruktuuriga.
Kuna pĂ€ringud OperativeQuestionsUserMail'i suhtes peaksid olema optimaalsed, omama optimaalseid pĂ€ringute plaane, on vajalik rangelt mÀÀratletud struktuur, mitte RETURNS TABLE AS RETURNâŠ
Antud juhul muudetakse otsitav PĂ€ring 1 PĂ€ringuks 4:
PĂ€ring nr 4
(@p__linq__0 nvarchar(4000))SELECT
1 AS [C1],
[Extent1].[Id] AS [Id],
[Join2].[Object_Id] AS [Object_Id],
[Join2].[ObjectType_Id] AS [ObjectType_Id],
[Join2].[Name] AS [Name],
[Join2].[ExternalId] AS [ExternalId]
FROM (
SELECT Id, Email FROM [dbo].[OperativeQuestionsUserMail] (@p__linq__0)
) AS [Extent0]
INNER JOIN [dbo].[Questions] AS [Extent1] ON([Extent0].Id=[Extent1].Id)
INNER JOIN (SELECT [Extent2].[Object_Id] AS [Object_Id], [Extent2].[Question_Id] AS [Question_Id], [Extent3].[ExternalId] AS [ExternalId], [Extent3].[ObjectType_Id] AS [ObjectType_Id], [Extent4].[Name] AS [Name]
FROM [dbo].[ObjectQuestions] AS [Extent2]
INNER JOIN [dbo].[Objects] AS [Extent3] ON [Extent2].[Object_Id] = [Extent3].[Id]
LEFT OUTER JOIN [dbo].[ObjectTypes] AS [Extent4]
ON [Extent3].[ObjectType_Id] = [Extent4].[Id] ) AS [Join2]
ON [Extent1].[Id] = [Join2].[Question_Id]
WHERE ([Extent1].[AnswerId] IS NULL) AND (0 = [Extent1].[Exp]);
Vaate ja funktsiooni mappimine DbContextis (EF Core 2)
public class QuestionsDbContext : DbContext
{
//...
public DbQuery<OperativeQuestion> OperativeQuestions { get; set; }
//...
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Query<OperativeQuestion>().ToView("OperativeQuestions");
}
}
public static class FromSqlQueries
{
public static IQueryable<OperativeQuestion> GetByUserEmail(this DbQuery<OperativeQuestion> source, string Email)
=> source.FromSql($"SELECT Id, Email FROM [dbo].[OperativeQuestionsUserMail] ({Email})");
}
LÔplik LINQ-pÀring
var businessObjectsData = await context
.OperativeQuestions
.GetByUserEmail(Email)
.Include(x => x.Question)
.Select(x => x.Question)
.SelectMany(x => x.ObjectQuestions,
(x, bo) => new
{
Id = x.Id,
ObjectId = bo.Object.Id,
ObjectTypeId = bo.Object.ObjectType.Id,
ObjectTypeName = bo.Object.ObjectType.Name,
ObjectExternalId = bo.Object.ExternalId
})
.ToListAsync();
Ajava tĂ€itmise aeg vĂ€henes 200-800 ms-lt 2-20 ms-ni, st kĂŒmneid kordi kiiremini.
Keskelt vÔttes saime 350 ms asemel 8 ms.
MÔningad ilmsed eelised on ka:
- ĂŒldine koormuse vĂ€henemine lugemisel,
- mÀrkimisvÀÀrne blokeeringute tÔenÀosuse vÀhenemine.
- keskmise blokeeringu aja vÀhendamine vastuvÔetavatesse vÀÀrtustesse.
KokkuvÔte
Andmebaasi pĂ€ringute optimeerimine ja hÀÀlestamine MS SQL lĂ€bi LINQ on ĂŒlesanne, mida on vĂ”imalik lahendada.
Selles protsessis on ÀÀrmiselt oluline tÀhelepanelikkus ja jÀrjepidevus.
Protsessi alguses:
- on vajalik kontrollida andmeid, millega pĂ€ring töötab (vÀÀrtused, valitud andmetĂŒĂŒbid).
- teha nende andmete Ôige indekseerimine.
- kontrollida tabelite vahelisi ĂŒhendustingimuste Ă”igsust.
JĂ€rgmiste optimeerimisetappide jooksul selguvad:
- pÀringu alus ja mÀÀratakse pÀringu pÔhiline filter.
- korduvad sarnased pĂ€ringu plokid ja analĂŒĂŒsitakse tingimuste ristumist.
- SSMS-is vÔi muus GUI-s. SQL Server optimeeritakse seejÀrel ise. SQL-pÀring (vaheandmete salvestuse eraldamine, tulemuse pÀringu koostamine selle salvestuse kasutamisel (vÔib olla mitu)).
- viimases etapis, vÔttes aluseks saadud tulemuse SQL-pÀring, rebuild the structure LINQ-pÀringust.
KokkuvÔttes peaks saadud LINQ-pÀring struktuur olema identne tuvastatud optimaalsega SQL-pÀring. punktist 3.
TĂ€nud
Suur tÀnu kolleegidele ja ettevÔttest Fortis abi eest selle materiaali ettevalmistamisel.
Allikas: habr.com
