Rêbazên xweşbînkirina pirsên LINQ di C#.NET de

Pîrozbahiyê

В vê gotara hin rêbazên optimîzasyonê hatin binçavkirin Pirsên LINQ.
Li vir em hin nêzîkatiyên din jî pêşkêşî xweşbînkirina kodê yên têkildar dikin Pirsên LINQ.

Zanîn ku LINQ(Language-Integrated Query) zimanek sade û rehet e ji bo lêpirsîna çavkaniyek daneyê.

А LINQ bo SQL teknolojiyek ji bo gihîştina daneyan di DBMS de ye. Ev amûrek hêzdar e ji bo xebata bi daneyan re, ku lêpirsîn bi zimanek daxuyandî têne çêkirin, ku paşê dê were veguheztin Pirsên SQL platform û ji bo darvekirinê şandin servera databasê. Di doza me de, mebesta me ji hêla DBMS ve ye Server SQL SQL.

Lêbelê, Pirsên LINQ nayên veguherandin yên herî baş ên nivîskî Pirsên SQL, ya ku DBA-yek ezmûndar dikare bi hemî nuwazeyên xweşbîniyê binivîsîne Pirsên SQL:

  1. girêdanên çêtirîn (BIHEVGIRÊDAN) û fîlterkirina encaman (KO)
  2. di karanîna girêdan û mercên komê de gelek nuwaze
  3. gelek guhertoyên li şûna şert û mercên IN li ser HEYEи NOT IN, <> li ser HEYE
  4. cachkirina navîn a encaman bi riya tabloyên demkî, CTE, guhêrbarên tabloyê
  5. bikaranîna hevokê (DIBE) bi talîmat û îşaretên tabloyê BI (...)
  6. bikaranîna dîtinên îndekskirî wekî yek ji navgînên xilasbûna ji xwendinên daneya zêde di dema hilbijartinê de

Girtiyên performansa sereke yên encam Pirsên SQL dema berhevkirinê Pirsên LINQ in:

  1. hevgirtina tevahiya mekanîzmaya hilbijartina daneyê di yek daxwazê ​​de
  2. dubarekirina blokên kodê yên wekhev, ku di dawiyê de dibe sedema xwendina gelek daneyên nepêwîst
  3. komên şertên pir-pêkhatî (mantiqî "û" û "an") - Û и OR, tevhevbûna di şert û mercên tevlihev de, rê li ber vê rastiyê vedike ku optimîzator, ku ji bo qadên pêwîst pêdekekên ne-komkirî yên guncav heye, di dawiyê de dest bi vekolîna li hember nîşana komkirî dike (PÊŞTIRKIRINA INDEX) ji hêla komên şertan ve
  4. hêlîna kûr a jêrpirsan parskirinê pir bi pirsgirêk dike Daxuyaniyên SQL û analîza plana pirsê ji hêla pêşdebiran û DBA

Rêbazên Optimîzasyonê

Naha em rasterast biçin ser rêbazên xweşbîniyê.

1) Indeksa zêde

Çêtir e ku meriv fîlteran li ser tabloyên hilbijartinê yên sereke bihesibîne, ji ber ku pir caran hemî pirs li dora yek an du tabloyên sereke (serlêdan-kes-operasyon) û bi komek şert û mercên standard (Girtî, Betalkirî, Çalak, Rewş) têne çêkirin. Girîng e ku ji bo nimûneyên naskirî îndeksên guncan werin afirandin.

Dema ku bijartina van qadan bi girîngî komek vegerî li pirsê sînordar dike ev çareserî maqûl e.

Mînak 500000 hezar serlêdanên me hene. Lêbelê, tenê 2000 serîlêdanên çalak hene. Wê hingê navnîşek rast-hilbijartî dê me jê xilas bike PÊŞTIRKIRINA INDEX li ser tabloyek mezin û dê bihêle ku hûn zû daneyan bi navgîniyek ne-kluster hilbijêrin.

Di heman demê de, kêmbûna îndeksan dikare bi rêgezên ji bo parkirina plansaziyên pirsê an berhevkirina statîstîkên dîtina pergalê were nas kirin. Server SQL SQL:

  1. sys.dm_db_missing_index_groups
  2. sys.dm_db_missing_index_group_stats
  3. sys.dm_db_missing_index_details

Hemî daneyên dîtinê ji xeynî navnîşanên cîhê, agahdariya li ser navnîşên winda hene.

Lêbelê, index û caching bi gelemperî rêgezên şerkirina encamên nebaş nivîsandinê ne Pirsên LINQ и Pirsên SQL.

Wekî ku pratîka dijwar a jiyanê nîşan dide, pir caran ji bo karsaziyek girîng e ku taybetmendiyên karsaziyê bi hin demsalan re bicîh bîne. Û ji ber vê yekê, daxwazên giran bi gelemperî bi cachkirinê re li paşnavê têne veguheztin.

Ev hinekî rastdar e, ji ber ku bikarhêner her gav ne hewceyê daneyên herî dawî ye û astek pejirandî ya bersivdayina navbeynkariya bikarhêner heye.

Ev nêzîkatî dihêle ku hewcedariyên karsaziyê çareser bike, lê di dawiyê de performansa pergala agahdariyê bi tenê derengxistina çareseriyên pirsgirêkan kêm dike.

Di heman demê de Hêjayî bibîrxistinê ye ku di pêvajoya lêgerîna pêvekên pêwîst de ku lê zêde bikin, pêşniyaran MS SQL optimîzekirin dibe ku xelet be, di nav şert û mercên jêrîn de:

  1. heke jixwe indexên bi komek zeviyên wekhev hebin
  2. heke qadên di tabloyê de ji ber qedexeyên îndekskirinê nikaribin werin navnîş kirin (bi hûrgulî têne diyar kirin vir).

2) Yekkirina taybetmendiyan li yek taybetmendiyek nû

Carinan hin qadên ji yek tabloyê, ku ji bo komek şertan wekî bingehek xizmet dikin, dikarin bi danasîna yek qadek nû werin guheztin.

Ev bi taybetî ji bo qadên statûyê, yên ku bi gelemperî bi tîpek bit an jî hejmar in rast e.

Nimûne:

Girtî = 0 Û Betalkirî = 0 Û Çalak = 0 tê guherandin Rewş = 1.

Li vir taybetmendiya statûya jimare tê destnîşan kirin da ku pê ewle bibe ku ev statû di tabloyê de hatine bicîh kirin. Piştre, ev taybetmendiya nû tête navnîş kirin.

Ev ji bo pirsgirêka performansê çareseriyek bingehîn e, ji ber ku Em bêyî hesabên nehewce digihîjin daneyan.

3) Materyalîzekirina dîtinê

Mixabin di Pirsên LINQ Tabloyên demkî, CTE, û guhêrbarên tabloyê rasterast nayên bikar anîn.

Lêbelê, rêyek din jî heye ku meriv ji bo vê dozê xweşbîn bike - dîtinên pêvekirî.

Koma şertê (ji mînaka li jor) Girtî = 0 Û Betalkirî = 0 Û Çalak = 0 (an komek şertên din ên wekhev) dibe vebijarkek baş ku meriv wan di dîmenek pêvekirî de bikar bîne, perçeyek piçûk a daneyê ji komek mezin veşêre.

Lê dema ku nêrînek pêk tê çend sînorkirin hene:

  1. bikaranîna jêrpirsiyan, bendan HEYE divê bi bikaranîna şûna BIHEVGIRÊDAN
  2. hûn nikarin hevokan bikar bînin Yektiya, YEKIONT ALL HEM, ÎSTÎSNA, JÊKIRIN
  3. Hûn nikarin nîşan û bendên tabloyê bikar bînin DIBE
  4. îmkana xebatê bi çerxeyan tune
  5. Ne gengaz e ku meriv daneyan di yek dîmenê de ji tabloyên cihêreng nîşan bide

Girîng e ku ji bîr mekin ku feydeya rastîn a karanîna dîmenek pêvekirî tenê bi rastkirina wê dikare were bidestxistin.

Lê dema gazîkirina dîtinekê, dibe ku ev index neyên bikar anîn, û ji bo ku wan bi eşkere bikar bînin, divê hûn diyar bikin BI (NOEXPAND).

Ji ber ku di Pirsên LINQ Ne gengaz e ku meriv pêşnîyarên tabloyê destnîşan bike, ji ber vê yekê hûn neçar in ku nûneriyek din biafirînin - "pêçek" bi forma jêrîn:

CREATE VIEW ИМЯ_представления AS SELECT * FROM MAT_VIEW WITH (NOEXPAND);

4) Karanîna fonksiyonên tabloyê

Gelek caran di Pirsên LINQ Blokên mezin ên jêrpirsan an blokên ku dîtinên bi avahiyek tevlihev bikar tînin pirsek dawîn bi avahiyek darvekirinê ya pir tevlihev û nebaş pêk tînin.

Feydeyên sereke yên karanîna fonksiyonên tabloyê di nav de Pirsên LINQ:

  1. Kapasîteya, wekî di rewşa dîtinan de, were bikar anîn û wekî objektek were destnîşan kirin, lê hûn dikarin komek pîvanên têketinê derbas bikin:
    FROM FUNCTION (@param1, @param2 ...)
    Wekî encamek, nimûneya daneya maqûl dikare were bidestxistin
  2. Di doza karanîna fonksiyonek tabloyê de, wekî ku di rewşa dîtinên pêvekirî yên ku li jor hatine destnîşan kirin de ti sînorkirinên wusa xurt tune:
    1. Nîşaneyên tabloyê:
      bi saya LINQ Hûn nekarin diyar bikin ka kîjan navnîşan divê werin bikar anîn û dema lêpirsînê asta veqetandina daneyê diyar bikin.
      Lê fonksiyona van îmkanan heye.
      Bi fonksiyonê re, hûn dikarin plansaziyek lêpirsînê ya darvekirinê ya domdar bi dest bixin, ku li wir qaîdeyên xebata bi îndeksan û astên veqetandina daneyê têne destnîşan kirin.
    2. Bikaranîna fonksiyonê dihêle, li gorî dîtinên pêvekirî, hûn bistînin:
      • mantiqa nimûneya daneya tevlihev (tevî bi karanîna lûpan)
      • wergirtina daneyan ji gelek tabloyên cihê
      • bikaranîna Yektiya и HEYE

  3. Pêşniyar dikin DIBE dema ku em hewce ne ku kontrola hevdemiyê peyda bikin pir bikêr e OPTION(MAXDOP N), fermana plana darvekirina pirsê. Bo nimûne:
    • hûn dikarin ji nû ve-afirandina zorê ya plana pirsê diyar bikin BİXWÎNE (BİXWÎNE BİXWÎNE)
    • hûn dikarin diyar bikin ka hûn plana lêpirsînê bi zorê bikin ku fermana tevlêbûnê ya ku di pirsnameyê de hatî destnîşan kirin bikar bîne BİXWÎNE (FERMANA ZORÊ)

    Agahiyên bêtir li ser DIBE şirove kirin vir.

  4. Bikaranîna parça daneya herî teng û herî pêwîst:
    Ne hewce ye ku berhevokên mezin ên daneyê di kaş de hilînin (wek ku di dîtinên pêvekirî de ye), ku hûn hîn jî hewce ne ku daneyan li gorî pîvanê fîlter bikin.
    Mînak tabloyek ku parzûna wê heye KO sê zevî têne bikaranîn (a, b, c).

    Bi kevneşopî, hemî daxwazî ​​şertek domdar in a = 0 û b = 0.

    Lêbelê, daxwaza zeviyê c guherbar zêdetir.

    Bila şertê a = 0 û b = 0 Ew bi rastî ji me re dibe alîkar ku em berhevoka encamên pêwîst bi hezaran tomar sînordar bikin, lê şert li ser heye с hilbijartinê bi sed qeydan teng dike.

    Li vir fonksiyona sifrê dibe ku vebijarkek çêtir be.

    Di heman demê de, fonksiyonek tabloyê di dema darvekirinê de pêşbîntir û domdartir e.

wergerandî

Werin em li pêkanîna mînakek bi karanîna databasa Pirsan wekî mînak binêrin.

Daxwazek heye NEQANDIN, ku çend tabloyan li hev dike û yek nêrînek (OperativeQuestions) bikar tîne, ku tê de girêdana bi e-nameyê tê kontrol kirin (bi rêya HEYE) ji "Pirsên Çalak" ([Pirsên Xebatê]):

Daxwaza Hejmar 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])
));

Nêrîn xwedan avahiyek pir tevlihev e: xwedan girêdanên binavkirî hene û cûrbecûr bikar tîne DISTINCT, ku bi gelemperî operasyonek bi çavkaniyek zehf e.

Nimûneyek ji OperativeQuestions bi qasî deh hezar tomar e.

Pirsgirêka sereke ya vê pirsê ev e ku ji bo tomarên ji pirsiyariya derve, jêrpirsînek navxweyî li ser dîtina [OperativeQuestions] tê darve kirin, ku divê ji bo [Email] = @p__linq__0 rê bide me ku em hilbijartina derketinê sînordar bikin (bi rêya HEYE) heta bi sedan tomar.

Û dibe ku wisa xuya bike ku pêdivî ye ku jêpirsîn tomaran carekê bi [Email] = @p__linq__0 hesab bike, û dûv re divê ev çend sed tomar bi Id-ê bi Pirsan ve werin girêdan, û pirs dê bilez be.

Di rastiyê de, pêwendiyek rêzdar a hemî tabloyan heye: kontrolkirina pêwendiya Pirsên Id-ê bi ID-ê ji OperativeQuestions, û fîlterkirina bi E-nameyê.

Bi rastî, daxwaz bi hemî deh hezaran tomarên OperativeQuestions re dixebite, lê tenê daneyên berjewendiyê bi E-nameyê re hewce dike.

OperativeQuestions nivîsê dibîne:

Daxwaza Hejmar 2

 
CREATE 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));

Nexşeya dîtina destpêkê li DbContext (EF Core 2)

public class QuestionsDbContext : DbContext
{
    //...
    public DbQuery<OperativeQuestion> OperativeQuestions { get; set; }
    //...
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Query<OperativeQuestion>().ToView("OperativeQuestions");
    }
}

Pirsa LINQ ya destpêkê

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();

Di vê rewşa taybetî de, em çareseriyek ji vê pirsgirêkê re bêyî guhertinên binesaziyê, bêyî danasîna tabloyek cihê bi encamên amade ("Pirsên Çalak"), ku pêdivî bi mekanîzmayek ji bo dagirtina wê bi daneyan û nûvekirina wê heye, dinirxînin. .

Her çend ev çareseriyek baş e, vebijarkek din jî heye ku vê pirsgirêkê xweşbîn bike.

Armanca sereke ew e ku ji dîtina OperativeQuestions tomarên bi [Email] = @p__linq__0 cache bike.

Fonksiyona tabloyê [dbo].[OperativeQuestionsUserMail] bike nav databasê.

Bi şandina E-nameyê wekî pîvanek têketinê, em tabloyek nirxan vedigerînin:

Daxwaza Hejmar 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

Ev tabloyek nirxan bi avahiyek daneya diyarkirî vedigerîne.

Ji bo ku pirsên ji OperativeQuestionsUserMail re çêtirîn be û xwedan plansaziyên lêpirsînê yên çêtirîn be, avahiyek hişk hewce ye, û ne MASÊ VEGERIYE WEK VEGERA...

Di vê rewşê de, Pirsa 1-ê ya pêwîst di Query 4-ê de tê veguheztin:

Daxwaza Hejmar 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]);

Nêrîn û fonksiyonên nexşeyê di DbContext (EF Core 2) de

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})");
}

Pirsa LINQ ya dawî

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();

Rêza dema darvekirinê ji 200-800 ms, daketiye 2-20 ms û hwd., ango bi dehan carî zûtir.

Ger em wê bi navgîntir bigirin, wê hingê li şûna 350 ms me 8 ms girt.

Ji avantajên eşkere em jî digirin:

  1. kêmkirina giştî ya barkirina xwendinê,
  2. kêmkirina girîng di îhtîmala astengkirinê de
  3. kêmkirina navînî dema astengkirina nirxên qebûl

encamê

Optimîzasyon û rastkirina bangên databasê MS SQL bi saya LINQ pirsgirêkek e ku dikare çareser bibe.

Di vî karî de baldarî û hevgirtin pir girîng e.

Di destpêka pêvajoyê de:

  1. Pêdivî ye ku daneyên ku daxwaz bi wan re dixebite kontrol bikin (nirx, celebên daneyên hilbijartî)
  2. îndekskirina rast a van daneyan pêk bînin
  3. rastbûna şertên tevlêbûna di navbera tabloyan de kontrol bikin

Dubarekirina optimîzasyona paşîn diyar dike:

  1. bingeha daxwazê ​​ye û parzûna daxwaza sereke diyar dike
  2. dubarekirina blokên pirsê yên wekhev û analîzkirina hevberdana şertan
  3. di SSMS an GUI-ya din de ji bo SQL Server xwe optîmîze dike Pirsa SQL (veqetandina hilanînek daneya navîn, avakirina pirsa encam bi karanîna vê hilanînê (dibe ku çend hebin))
  4. di qonaxa dawî de, encaman esas digire Pirsa SQL, avahî ji nû ve tê avakirin Lêpirsîna LINQ

Encam Lêpirsîna LINQ divê di strukturê de bi optîmala naskirî re bibe yek Pirsa SQL ji xala 3.

Spasdarî

Gelek spas ji bo hevkaran jobgemws и alex_ozr ji şîrketa Fortis ji bo alîkariyê di amadekirina vê materyalê de.

Source: www.habr.com

Add a comment