Mbinu za kuboresha hoja za LINQ katika C#.NET

Utangulizi

Π’ Makala hii baadhi ya mbinu za uboreshaji zilizingatiwa maswali ya LINQ.
Hapa pia tunawasilisha njia zingine zaidi za uboreshaji wa nambari zinazohusiana na maswali ya LINQ.

Inajulikana kwamba LINQ(Hoja Iliyounganishwa kwa Lugha) ni lugha rahisi na rahisi ya kuuliza chanzo cha data.

А LINQ kwa SQL ni teknolojia ya kupata data katika DBMS. Hii ni zana yenye nguvu ya kufanya kazi na data, ambapo maswali yanaundwa kupitia lugha ya kutangaza, ambayo itabadilishwa kuwa Maswali ya SQL jukwaa na kutumwa kwa seva ya hifadhidata kwa utekelezaji. Kwa upande wetu, kwa DBMS tunamaanisha MS SQL Server.

Hata hivyo, maswali ya LINQ hazijabadilishwa kuwa zile zilizoandikwa kikamilifu Maswali ya SQL, ambayo DBA yenye uzoefu inaweza kuandika na nuances zote za utoshelezaji Maswali ya SQL:

  1. miunganisho bora (JOIN) na kuchuja matokeo (WAPI)
  2. nuances nyingi katika kutumia viunganisho na hali ya kikundi
  3. tofauti nyingi katika kubadilisha hali IN juu ya IPOΠΈ HAKUNA, <> imewashwa IPO
  4. Caching ya kati ya matokeo kupitia majedwali ya muda, CTE, vigezo vya jedwali
  5. matumizi ya sentensi (OPTION) na maagizo na vidokezo vya jedwali NA (...)
  6. kutumia mionekano iliyoorodheshwa kama mojawapo ya njia za kuondoa usomaji wa data usiohitajika wakati wa uteuzi

Vikwazo kuu vya utendaji wa matokeo Maswali ya SQL wakati wa kuandaa maswali ya LINQ ni:

  1. ujumuishaji wa utaratibu mzima wa uteuzi wa data katika ombi moja
  2. kunakili vizuizi vinavyofanana vya msimbo, ambayo hatimaye husababisha usomaji wa data nyingi zisizo za lazima
  3. vikundi vya hali ya vipengele vingi (mantiki "na" na "au") - NA ΠΈ OR, ikichanganya katika hali ngumu, inaongoza kwa ukweli kwamba kiboreshaji, kuwa na faharisi zinazofaa zisizo na nguzo kwa uwanja muhimu, hatimaye huanza kuchambua dhidi ya faharisi iliyounganishwa (INDEX SCAN) kwa makundi ya masharti
  4. kuota kwa kina kwa maswali madogo hufanya uchanganuzi kuwa shida sana Taarifa za SQL na uchambuzi wa mpango wa swala kwa upande wa watengenezaji na DBA

Njia za kuboresha

Sasa hebu tuende moja kwa moja kwa njia za uboreshaji.

1) Uorodheshaji wa ziada

Ni bora kuzingatia vichungi kwenye meza kuu za uteuzi, kwani mara nyingi swala zima hujengwa karibu na jedwali moja au mbili kuu (programu-utendaji-watu) na kwa seti ya hali ya kawaida (Imefungwa, Imeghairiwa, Imewezeshwa, Hali). Ni muhimu kuunda fahirisi zinazofaa kwa sampuli zilizotambuliwa.

Suluhisho hili linaeleweka wakati wa kuchagua sehemu hizi kwa kiasi kikubwa hupunguza seti iliyorejeshwa kwa hoja.

Kwa mfano, tuna maombi 500000. Walakini, kuna programu 2000 tu zinazotumika. Kisha faharisi iliyochaguliwa kwa usahihi itatuokoa kutoka INDEX SCAN kwenye jedwali kubwa na itakuruhusu kuchagua data haraka kupitia faharisi isiyo na nguzo.

Pia, ukosefu wa faharasa unaweza kutambuliwa kupitia vidokezo vya kuchanganua mipango ya hoja au kukusanya takwimu za mtazamo wa mfumo. MS SQL Server:

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

Data yote ya mwonekano ina taarifa kuhusu faharasa zinazokosekana, isipokuwa faharasa za anga.

Walakini, faharisi na caching mara nyingi ni njia za kupambana na matokeo ya kuandikwa vibaya maswali ya LINQ ΠΈ Maswali ya SQL.

Kama mazoezi makali ya maisha yanavyoonyesha, mara nyingi ni muhimu kwa biashara kutekeleza vipengele vya biashara kwa muda fulani. Na kwa hiyo, maombi mazito mara nyingi huhamishiwa nyuma na caching.

Hii inahesabiwa haki, kwani mtumiaji hahitaji kila wakati data ya hivi punde na kuna kiwango kinachokubalika cha mwitikio wa kiolesura cha mtumiaji.

Mbinu hii inaruhusu kutatua mahitaji ya biashara, lakini hatimaye hupunguza utendaji wa mfumo wa habari kwa kuchelewesha tu ufumbuzi wa matatizo.

Inafaa pia kukumbuka kuwa katika mchakato wa kutafuta faharisi muhimu za kuongeza, mapendekezo MS SQL uboreshaji unaweza kuwa sio sahihi, pamoja na chini ya masharti yafuatayo:

  1. ikiwa tayari kuna faharasa zilizo na seti sawa ya sehemu
  2. ikiwa sehemu kwenye jedwali haziwezi kuorodheshwa kwa sababu ya vizuizi vya kuorodhesha (zilizofafanuliwa kwa undani zaidi hapa).

2) Kuunganisha sifa katika sifa moja mpya

Wakati mwingine baadhi ya mashamba kutoka kwa meza moja, ambayo hutumika kama msingi wa kundi la masharti, yanaweza kubadilishwa kwa kuanzisha uwanja mmoja mpya.

Hii ni kweli hasa kwa sehemu za hali, ambazo kwa kawaida huwa na aina ndogo au kamili.

Mfano:

Imefungwa = 0 NA Imeghairiwa = 0 NA Imewashwa = 0 inabadilishwa na Hali = 1.

Hapa ndipo sifa kamili ya Hali inapoanzishwa ili kuhakikisha kuwa hali hizi zimewekwa kwenye jedwali. Ifuatayo, sifa hii mpya imeorodheshwa.

Hili ni suluhu la msingi kwa tatizo la utendakazi, kwa sababu Tunapata data bila hesabu zisizo za lazima.

3) Uboreshaji wa mtazamo

Kwa bahati mbaya, katika maswali LINQ Jedwali la muda, CTEs, na vigezo vya jedwali haviwezi kutumika moja kwa moja.

Walakini, kuna njia nyingine ya kuboresha kesi hii - maoni yaliyoonyeshwa.

Kikundi cha masharti (kutoka kwa mfano hapo juu) Imefungwa = 0 NA Imeghairiwa = 0 NA Imewashwa = 0 (au seti ya hali zingine zinazofanana) inakuwa chaguo nzuri kuzitumia katika mwonekano wa faharasa, ikihifadhi kipande kidogo cha data kutoka kwa seti kubwa.

Lakini kuna idadi ya vizuizi wakati wa kuunda mtazamo:

  1. matumizi ya subqueries, vifungu IPO inapaswa kubadilishwa kwa kutumia JOIN
  2. huwezi kutumia sentensi UNION, UNION YOTE, UFUNZO, Kuingiliana
  3. Huwezi kutumia vidokezo vya meza na vifungu OPTION
  4. hakuna uwezekano wa kufanya kazi na mizunguko
  5. Haiwezekani kuonyesha data katika mtazamo mmoja kutoka kwa meza tofauti

Ni muhimu kukumbuka kuwa faida halisi ya kutumia mtazamo uliowekwa kwenye faharasa inaweza kupatikana tu kwa kuuweka kwenye faharasa.

Lakini unapoita mtazamo, faharisi hizi haziwezi kutumiwa, na kuzitumia kwa uwazi, lazima ueleze NA(NOEXPAND).

Tangu katika maswali LINQ Haiwezekani kufafanua vidokezo vya jedwali, kwa hivyo lazima uunda uwakilishi mwingine - "kifuniko" cha fomu ifuatayo:

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

4) Kutumia kazi za meza

Mara nyingi katika maswali LINQ Vizuizi vikubwa vya maswali madogo au vizuizi kwa kutumia maoni yenye muundo changamano huunda hoja ya mwisho yenye muundo tata sana na usiofaa zaidi wa utekelezaji.

Faida Muhimu za Kutumia Kazi za Jedwali katika maswali LINQ:

  1. Uwezo, kama ilivyo kwa maoni, kutumika na kubainishwa kama kitu, lakini unaweza kupitisha seti ya vigezo vya ingizo:
    KUTOKA KWA KAZI(@param1, @param2 ...)
    Matokeo yake, sampuli rahisi za data zinaweza kupatikana
  2. Katika kesi ya kutumia kazi ya jedwali, hakuna vizuizi vikali kama ilivyo kwa maoni yaliyoelezewa hapo juu:
    1. Vidokezo vya jedwali:
      kupitia LINQ Huwezi kubainisha ni faharasa zipi zinafaa kutumika na kubainisha kiwango cha kutenga data wakati wa kuuliza.
      Lakini kazi ina uwezo huu.
      Ukiwa na chaguo hili, unaweza kufikia mpango wa swala la utekelezaji wa kila mara, ambapo sheria za kufanya kazi na faharisi na viwango vya kutengwa kwa data hufafanuliwa.
    2. Kutumia chaguo la kukokotoa huruhusu, kwa kulinganisha na maoni yaliyoorodheshwa, kupata:
      • mantiki changamano ya sampuli za data (hata kutumia vitanzi)
      • kuchota data kutoka kwa meza nyingi tofauti
      • matumizi ya UNION ΠΈ IPO

  3. Kutoa OPTION muhimu sana tunapohitaji kutoa udhibiti wa concurrency CHAGUO(MAXDOP N), utaratibu wa mpango wa utekelezaji wa hoja. Kwa mfano:
    • unaweza kutaja uundaji upya wa kulazimishwa wa mpango wa hoja CHAGUO (RECOMPILE)
    • unaweza kubainisha kama kulazimisha mpango wa hoja kutumia agizo la kujiunga lililobainishwa kwenye hoja CHAGUO (LAZIMI AGIZO)

    Maelezo zaidi kuhusu OPTION ilivyoelezwa hapa.

  4. Kwa kutumia kipande cha data chembamba na kinachohitajika zaidi:
    Hakuna haja ya kuhifadhi seti kubwa za data katika cache (kama ilivyo kwa maoni yaliyoonyeshwa), ambayo bado unahitaji kuchuja data kwa parameter.
    Kwa mfano, kuna meza ambayo chujio chake WAPI nyanja tatu zinatumika (a, b, c).

    Kwa kawaida, maombi yote yana hali ya mara kwa mara a = 0 na b = 0.

    Walakini, ombi la uwanja c kutofautiana zaidi.

    Wacha hali hiyo a = 0 na b = 0 Inatusaidia sana kuweka kikomo cha matokeo yanayohitajika kwa maelfu ya rekodi, lakini sharti liendelee с hupunguza uteuzi hadi rekodi mia moja.

    Hapa kazi ya meza inaweza kuwa chaguo bora zaidi.

    Pia, kazi ya jedwali inaweza kutabirika zaidi na thabiti katika wakati wa utekelezaji.

mifano

Wacha tuangalie utekelezaji wa mfano kwa kutumia hifadhidata ya Maswali kama mfano.

Kuna ombi SELECT, ambayo inachanganya majedwali kadhaa na kutumia mwonekano mmoja (OperativeQuestions), ambamo ushirika unaangaliwa kwa barua pepe (kupitia IPO) hadi "Maswali ya Uendeshaji":

Ombi Nambari 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])
));

Mtazamo una muundo mgumu zaidi: una viunganishi vya subquery na hutumia kupanga DISTINCT, ambayo kwa ujumla ni operesheni inayohitaji rasilimali nyingi.

Sampuli kutoka kwa OperesheniQuestions ni takriban rekodi elfu kumi.

Shida kuu ya swali hili ni kwamba kwa rekodi kutoka kwa hoja ya nje, hoja ndogo ya ndani inatekelezwa kwenye mwonekano wa [OperesheniQuestions], ambayo inapaswa kwa [Email] = @p__linq__0 kuturuhusu kuweka kikomo cha uteuzi wa matokeo (kupitia IPO) hadi mamia ya rekodi.

Na inaweza kuonekana kuwa hoja ndogo inapaswa kukokotoa rekodi mara moja kwa [Barua pepe] = @p__linq__0, kisha rekodi hizi mia kadhaa ziunganishwe kwa Id na Maswali, na hoja itakuwa haraka.

Kwa kweli, kuna muunganisho mfuatano wa majedwali yote: kuangalia mawasiliano ya Maswali ya Kitambulisho na Kitambulisho kutoka kwa Maswali ya Uendeshaji, na kuchuja kwa Barua pepe.

Kwa hakika, ombi hufanya kazi na makumi ya maelfu ya rekodi za OperesheniQuestions, lakini ni data ya manufaa pekee inayohitajika kupitia Barua pepe.

Maswali ya Uendeshaji tazama maandishi:

Ombi Nambari 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));

Mtazamo wa awali wa ramani katika 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");
    }
}

Hoja ya awali ya LINQ

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

Katika kesi hii, tunazingatia suluhisho la tatizo hili bila mabadiliko ya miundombinu, bila kuanzisha jedwali tofauti na matokeo yaliyotengenezwa tayari ("Maswali Yanayotumika"), ambayo yatahitaji utaratibu wa kuijaza na data na kusasisha. .

Ingawa hii ni suluhisho nzuri, kuna chaguo jingine la kuboresha tatizo hili.

Kusudi kuu ni kuweka akiba maingizo kwa [Barua pepe] = @p__linq__0 kutoka kwa mtazamo wa OperesheniQuestions.

Tambulisha kitendakazi cha jedwali [dbo].[OperativeQuestionsUserMail] kwenye hifadhidata.

Kwa kutuma Barua pepe kama kigezo cha ingizo, tunarudisha jedwali la maadili:

Ombi Nambari 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

Hii inarejesha jedwali la maadili na muundo wa data ulioainishwa awali.

Ili maswali kwa OperativeQuestionsUserMail yawe bora na yawe na mipango bora ya kuuliza, muundo madhubuti unahitajika, na sio. INARUDISHA MEZA KAMA KURUDISHA...

Katika kesi hii, Hoja ya 1 inayohitajika inabadilishwa kuwa Hoja ya 4:

Ombi Nambari 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]);

Kuchora maoni na utendakazi katika 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");
    }
}
 
public static class FromSqlQueries
{
    public static IQueryable<OperativeQuestion> GetByUserEmail(this DbQuery<OperativeQuestion> source, string Email)
        => source.FromSql($"SELECT Id, Email FROM [dbo].[OperativeQuestionsUserMail] ({Email})");
}

Hoja ya mwisho ya LINQ

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

Utaratibu wa muda wa utekelezaji umeshuka kutoka 200-800 ms, hadi 2-20 ms, nk, yaani mara kumi kwa kasi zaidi.

Ikiwa tunaichukua kwa wastani zaidi, basi badala ya 350 ms tulipata 8 ms.

Kutoka kwa faida dhahiri tunapata pia:

  1. kupungua kwa jumla kwa mzigo wa kusoma;
  2. kupunguza kwa kiasi kikubwa uwezekano wa kuzuia
  3. kupunguza wastani wa muda wa kuzuia hadi maadili yanayokubalika

Pato

Uboreshaji na urekebishaji mzuri wa simu za hifadhidata MS SQL kupitia LINQ ni tatizo linaloweza kutatuliwa.

Usikivu na uthabiti ni muhimu sana katika kazi hii.

Mwanzoni mwa mchakato:

  1. inahitajika kuangalia data ambayo ombi hufanya kazi (maadili, aina za data zilizochaguliwa)
  2. kutekeleza indexing sahihi ya data hii
  3. angalia usahihi wa masharti ya kuunganisha kati ya meza

Uboreshaji unaofuata unaonyesha:

  1. msingi wa ombi na kufafanua kichujio kikuu cha ombi
  2. kurudia vizuizi vya swala sawa na kuchambua makutano ya masharti
  3. katika SSMS au GUI nyingine ya SQL Server inajiboresha yenyewe Swali la SQL (kugawa hifadhi ya data ya kati, kuunda hoja inayotokana na hifadhi hii (kunaweza kuwa kadhaa))
  4. katika hatua ya mwisho, ikichukua kama msingi wa matokeo Swali la SQL, muundo unajengwa upya Swali la LINQ

matokeo Swali la LINQ inapaswa kufanana katika muundo na mojawapo iliyotambuliwa Swali la SQL kutoka pointi 3.

Shukrani

Shukrani nyingi kwa wenzake jobgemws ΠΈ alex_ozr kutoka kwa kampuni Fortis kwa msaada katika kuandaa nyenzo hii.

Chanzo: mapenzi.com

Kuongeza maoni