web-dev-qa-db-fra.com

Optimisation de la fonction valorisée SQL Server

J'essaie d'optimiser cette fonction de valorisation de la table. Si je pouvais, je le changerais à la procédure mais je ne peux pas. Le problème est avec deux états de mise à jour. Je n'ai gardé que ces deux dans la fonction parce qu'ils causent les principaux problèmes de performance. J'ai réécrit le premier de l'externe s'appliquant à la jointure intérieure et j'ai examiné les statistiques et ils ont mal que j'ai tellement ajouté une option (recompiler) et cela a beaucoup contribué. Le problème est dans la deuxième mise à jour. Les statistiques sont fausses et je ne sais pas comment faire un plan d'exécution approprié et l'optimiser avec des allusions. Vous s'il vous plaît avoir une idée comment obtenir le temps de temps? J'ai essayé de indexer la variable de table mais sans résultat.

Voici un plan d'exécution https://www.brentozar.com/pastetheplan/?id=b1edbo5E4

Merci.

CREATE FUNCTION [dbo].[cfn_PlanServis_Seznam](
   @IDVazRole INT,
   @IDUzivatel INT,
   @IDRole INT,
   @IDLokalita INT,
   @lCid INT
)

RETURNS @PlanServis TABLE(
   lIDAuto INT,
   szSPZ VARCHAR(100),
   lDepozit INT,
   szTypVozidla varchar(100),
   szTypServisu NVARCHAR(300),
   szServisniPlan NVARCHAR(300),
   lZbyvaDni INT,
   lZbyvaKm INT,
   lNajetoKm INT,
   dtServis DATETIME,
   dcZbyvaMotohodin DECIMAL(15,1),
   dcNajetoMotohodin DECIMAL(15,1),
   IDVazPlanServisAuto INT,
   IDPlanServisDefinice INT,
   lBarva INT

)

AS

BEGIN
    DECLARE @Auto TABLE(
       lIDAuto INT,
       szSPZ VARCHAR(100),
       szTyp VARCHAR(100),
       IDCisTypServis INT,
       szTypServisu NVARCHAR(500),
       szServisniPlan NVARCHAR(500),
       lKmStart INT,
       dtStart DATETIME,
       lKmPriZavedeni INT,
       lUjetoPredZavedenim INT,
       dcMotohodinyStart DECIMAL(15,1),
       lIntervalKm INT,
       dcIntervalMotohodiny DECIMAL(15,1),
       lUjeto INT,
       dcMotohodiny DECIMAL(15,1),
       IDServis INT,
       lKmServis INT,
       dcMotohodinyServis DECIMAL(15,1),
       dtServis DATETIME,
       lIntervalDatum INT,
       lDniUbehlo INT,
       lBarva INT,
       lZbyvaKm INT,
       dcZbyvaMotohodin DECIMAL(15,2),
       lZbyvaDni INT,
       lDepozit INT,
       IDVazPlanServisAuto INT,
       IDPlanServisDefinice INT,
       lMaxTachograf INT,
       lKmPretaceni INT,
       dtOd DATE,
       lKmPosledniServis INT
    )

    DECLARE @IDCisAutoParametrKmPriZavadeni INT = 10012
    DECLARE @lKmPred INT = 30000
    DECLARE @lKmPredMensi INT = 15000
    DECLARE @lDniPred INT = 60
    DECLARE @lDniPredMensi INT = 30
    DECLARE @lMotohodinyPred INT = 100
    DECLARE @lMotohodinyPredMensi INT = 50
    DECLARE @IDBarvaBlizi INT = 1010 --Odkaz do CisTermBarva
    DECLARE @IDBarvaBliziMensi INT = 1016 --Odkaz do CisTermBarva
    DECLARE @IDBarvaPres INT  = 1017 --Odkaz do CisTermBarva
    --============ Koenc deklarace promennych ===========

    INSERT INTO @Auto (lIDAuto, szSPZ, szTyp, IDCisTypServis, szTypServisu, lKmStart, dtStart, lKmPriZavedeni, dcMotohodinyStart,[@Auto].lIntervalKm,[@Auto].dcIntervalMotohodiny,[@Auto].lIntervalDatum,szServisniPlan,[@Auto].IDVazPlanServisAuto,[@Auto].IDPlanServisDefinice)
    SELECT Auto.lIDAuto,
           Auto.szSpz,
           CASE WHEN Auto.lTyp = 0 THEN 'Taha?'  WHEN Auto.lTyp =  1 THEN 'N?v?s' ELSE '' END,
           PlanServisDefinice.IDCisTypServis,
           dbo.GetLocalText('CisTypServis','szNazev',CisTypServis.lIDCisTypServis,@lCid,CisTypServis.szNazev,''),
           PlanServisDefinice.lStartKM,
           PlanServisDefinice.dtStartDatum,
           CONVERT(INT,VazAutoParametr.varHodnota),
           PlanServisDefinice.dcMotohodinyStart,
           PlanServisDefinice.lIntervalKM,
           PlanServisDefinice.dcIntervalMotohodin,
           PlanServisDefinice.lIntervalDatum,
           PlanServis.szNazev,
           PlanServisDefinice.IDVazPlanServisAuto,
           PlanServisDefinice.lIDPlanServisDefinice
    FROM Auto INNER JOIN VazPlanServisAuto ON Auto.lIDAuto = VazPlanServisAuto.IDAuto
             INNER JOIN PlanServisDefinice ON VazPlanServisAuto.lIDVazPlanServisAuto = PlanServisDefinice.IDVazPlanServisAuto   
             INNER JOIN CisTypServis ON PlanServisDefinice.IDCisTypServis = CisTypServis.lIDCisTypServis
             LEFT OUTER JOIN VazAutoParametr ON VazAutoParametr.IDAuto = Auto.lIDAuto AND VazAutoParametr.IDCisAutoParametr = @IDCisAutoParametrKmPriZavadeni
             INNER JOIN PlanServis ON VazPlanServisAuto.IDPlanServisu = PlanServis.lIDPlanServis


    UPDATE @Auto SET lUjeto = Km.lKm
        FROM @Auto INNER JOIN 
        (SELECT
                    SUM(JizdaTachograf.lkmDo - JizdaTachograf.lkmOd) AS lKm, JizdaTachograf.IDAuto,JizdaTachograf.IDNaves
        FROM Jizda
            INNER JOIN JizdaTachograf ON JizdaTachograf.IDJizda = Jizda.lIDJizda
        WHERE JizdaTachograf.lkmOd IS NOT NULL
            AND JizdaTachograf.lkmDo IS NOT NULL
            AND Jizda.lProvozne = 1
        GROUP BY 
            JizdaTachograf.IDAuto,JizdaTachograf.IDNaves    
        ) as Km 
            ON Km.IDAuto = [@Auto].lIDAuto OR Km.IDNaves = [@Auto].lIDAuto
            OPTION  (RECOMPILE)


    UPDATE @Auto SET lMaxTachograf = ISNULL(ISNULL(Km.lKm, [@Auto].lKmServis),[@Auto].lKmStart)
    FROM @Auto 
         OUTER APPLY 
    (SELECT TOP 1 JizdaTachograf.lkmDo lKm 
     FROM Jizda INNER JOIN 
     JizdaTachograf ON JizdaTachograf.IDJizda = Jizda.lIDJizda 
     WHERE JizdaTachograf.lkmOd IS NOT NULL AND 
           JizdaTachograf.lkmDo IS NOT NULL AND Jizda.lProvozne = 1 
           AND (JizdaTachograf.IDAuto = [@Auto].lIDAuto 
           OR JizdaTachograf.IDNaves = [@Auto].lIDAuto) 
           AND Jizda.dtZacatek > ISNULL(ISNULL([@Auto].dtServis, 
           [@Auto].dtStart),DATEADD(YEAR,-100,GETDATE())) 
     ORDER BY 
           Jizda.dtZacatek DESC, JizdaTachograf.lkmDo desc) Km



    INSERT INTO @PlanServis (lIDAuto, 
                             szSPZ, 
                             lDepozit, 
                             szTypVozidla, 
                             szTypServisu, 
                             szServisniPlan, 
                             lZbyvaDni, 
                             lZbyvaKm, 
                             lNajetoKm, 
                             dtServis, 
                             dcZbyvaMotohodin, 
                             dcNajetoMotohodin, 
                             IDVazPlanServisAuto, 
                             IDPlanServisDefinice,
                             lBarva)
    SELECT lIDAuto, 
            szSPZ, 
            lDepozit, 
            szTyp, 
            szTypServisu, 
            szServisniPlan, 
            lZbyvaDni, 
            lZbyvaKm, 
            lUjeto,--lNajetoKm, 
            dtServis, 
            dcZbyvaMotohodin, 
            dcMotohodiny,--dcNajetoMotohodin, 
            IDVazPlanServisAuto, 
            IDPlanServisDefinice,
            lBarva
      FROM @Auto 
    RETURN
END
GO
4
jindrich

Pour une question comme celle-ci, il est très utile de fournir une [~ # ~ # ~ # ~] . Comme je devais gagner beaucoup de suppositions sur la structure de la table et la distribution des données. Vous dites que cette partie des plans de requête est trop lente sans autre élaboration:

old query plan

Je peux voir trois raisons pour lesquelles cette partie pourrait être lente. Le premier problème est que les deux tables ne disposent que de 176 000 lignes totales. Pourtant, l'index cherche à tirer plus de 800 000 rangées des deux tables. Le deuxième problème est que l'indice rechercher sur Jizdatachograf n'atteint que le prédicat suivant: [Lori_MDL].[dbo].[JizdaTachograf].lkmOd IS NOT NULL. Je suppose que cela pourrait être sélectif, mais sinon, vous numérisez efficacement la plupart des tables d'index 845. Le troisième numéro est au total de 800k lignes sont triés, bien que les sortes soient divisées en 846 itérations.

Il pourrait y avoir un moyen d'obtenir un plan qui fait simplement une seule analyse des deux tables, mais sans comprendre les distributions de données que je ne sais pas si cela en valait la peine. Les exigences de votre requête (inégalités, tri, OR logique) rendent difficile pour une jointure de fusion ou une jointure de hachage à travailler.

Un problème que vous pouvez résoudre est le second. Si vous définissez les bons indices et séparez (JizdaTachograf.IDAuto = [@Auto].lIDAuto OR JizdaTachograf.IDNaves = [@Auto].lIDAuto) En deux sous-requêtes, vous pouvez obtenir plus d'index plus efficace sur JizdaTachograf qui recherchent directement les lignes concernées. Cela pourrait économiser beaucoup de temps si la plupart des rangées de la table ont des valeurs non nulles pour lkmOd. Il existe de nombreuses définitions d'index différents pouvant fonctionner. Deux sont ci-dessous:

CREATE INDEX IX2 ON JizdaTachograf (IDAuto, IDJizda, lkmDo) INCLUDE (lkmOd)
WHERE lkmOd IS NOT NULL AND lkmDo IS NOT NULL;

CREATE INDEX IX3 ON JizdaTachograf (IDNaves, IDJizda, lkmDo) INCLUDE (lkmOd)
WHERE lkmOd IS NOT NULL AND lkmDo IS NOT NULL;

J'ai ensuite séparé la requête afin que SQL Server puisse tirer parti des index.

UPDATE @Auto SET lMaxTachograf = ISNULL(ISNULL(Km.lKm, [@Auto].lKmServis),[@Auto].lKmStart)
    FROM @Auto 
    OUTER APPLY 
    (
    SELECT TOP (1) lKm
    FROM
    (

    SELECT TOP (1) Jizda.dtZacatek, JizdaTachograf.lkmDo lKm 
     FROM JizdaTachograf
     INNER JOIN Jizda WITH (INDEX(1)) ON JizdaTachograf.IDJizda = Jizda.lIDJizda 
     WHERE JizdaTachograf.lkmOd IS NOT NULL AND 
           JizdaTachograf.lkmDo IS NOT NULL AND Jizda.lProvozne = 1 
           AND JizdaTachograf.IDAuto = [@Auto].lIDAuto -- first half
           AND Jizda.dtZacatek > ISNULL(ISNULL([@Auto].dtServis, [@Auto].dtStart),DATEADD(YEAR,-100,GETDATE())) 
     ORDER BY  Jizda.dtZacatek DESC, JizdaTachograf.lkmDo desc

     UNION ALL

     SELECT TOP (1) Jizda.dtZacatek, JizdaTachograf.lkmDo lKm 
     FROM JizdaTachograf
     INNER JOIN Jizda WITH (INDEX(1)) ON JizdaTachograf.IDJizda = Jizda.lIDJizda 
     WHERE JizdaTachograf.lkmOd IS NOT NULL AND 
           JizdaTachograf.lkmDo IS NOT NULL AND Jizda.lProvozne = 1 
           AND JizdaTachograf.IDNaves = [@Auto].lIDAuto -- second half
           AND Jizda.dtZacatek > ISNULL(ISNULL([@Auto].dtServis, [@Auto].dtStart),DATEADD(YEAR,-100,GETDATE())) 
     ORDER BY Jizda.dtZacatek DESC, JizdaTachograf.lkmDo desc
    ) IDNaves_IDAuto
    ORDER BY dtZacatek DESC, lKm DESC   
    ) Km;

Je travaille avec des tables vides, mais je peux montrer qu'il est au moins possible d'obtenir la forme du plan souhaité:

plan 1

L'avantage de ce plan est qu'il va faire moins IO sur JizdaTachograf et que les sortes sont divisées encore plus loin. Cependant, vous tirez toujours le même nombre de lignes des index et triant le même nombre total de lignes.

Il est possible d'écrire cette requête pour qu'il n'y ait pas de tri. Le IO Motif est différent, ce qui pourrait entraîner moins de lecture globale. Vous aurez besoin d'un autre index. Vous trouverez ci-dessous un élément qui fonctionne:

CREATE INDEX IX1 ON Jizda (dtZacatek) INCLUDE (lIDJizda, lProvozne)
WHERE lProvozne = 1;

L'optimiseur ne peut pas toujours faire les mêmes conclusions que nous pouvons sur les données triées, j'ai donc changé la requête pour que cela comprenne que le tri n'est pas nécessaire:

UPDATE @Auto SET lMaxTachograf = ISNULL(ISNULL(Km.lKm, [@Auto].lKmServis),[@Auto].lKmStart)
    FROM @Auto 
    OUTER APPLY 
    (
    SELECT TOP (1) lkmDo lKm
    FROM
    (
        SELECT TOP (1) Jizda.dtZacatek, ca.lkmDo
        FROM Jizda 
        CROSS APPLY (
             SELECT TOP (1) JizdaTachograf.lkmDo
             FROM JizdaTachograf
             WHERE JizdaTachograf.IDJizda = Jizda.lIDJizda 
             AND JizdaTachograf.lkmOd IS NOT NULL AND 
             JizdaTachograf.lkmDo IS NOT NULL
             AND JizdaTachograf.IDNaves = [@Auto].lIDAuto  -- this line is different    
             ORDER BY JizdaTachograf.lkmDo DESC          
        ) ca
        WHERE Jizda.lProvozne = 1 
        AND Jizda.dtZacatek > ISNULL(ISNULL([@Auto].dtServis,[@Auto].dtStart),DATEADD(YEAR,-100,GETDATE())) 
        ORDER BY Jizda.dtZacatek DESC

        UNION ALL

        SELECT TOP (1) Jizda.dtZacatek, ca.lkmDo
        FROM Jizda 
        CROSS APPLY (
             SELECT TOP (1) JizdaTachograf.lkmDo
             FROM JizdaTachograf
             WHERE JizdaTachograf.IDJizda = Jizda.lIDJizda 
             AND JizdaTachograf.lkmOd IS NOT NULL AND 
             JizdaTachograf.lkmDo IS NOT NULL
             AND JizdaTachograf.IDAuto = [@Auto].lIDAuto -- this line is different  
             ORDER BY JizdaTachograf.lkmDo DESC          
        ) ca
        WHERE Jizda.lProvozne = 1 
        AND Jizda.dtZacatek > ISNULL(ISNULL([@Auto].dtServis,[@Auto].dtStart),DATEADD(YEAR,-100,GETDATE())) 
        ORDER BY Jizda.dtZacatek DESC
    )  IDNaves_IDAuto

    ORDER BY dtZacatek DESC, lkmDo DESC
    ) Km

Maintenant, il n'y a pas de tri:

plan 2

Cependant, c'est un peu une optimisation dangereuse à faire. Maintenant Jizda est la table extérieure pour la jointure imbriquée de la boucle. Considérons une ligne dans @Auto avec null pour [@Auto].dtServis, Null pour [@Auto].dtStart et pas de matchs contre JizdaTachograf par IDNaves et IDAuto. SQL Server lira via toutes les lignes de 180 000 en Jizda et do 180k Index cherche contre JizdaTachograf pour ne renvoyer finalement aucune ligne de lignes. Je ne sais pas quelle est la probabilité que cela se produise, mais cela pourrait arriver.

Sur la base des informations fournies dans la question, mon conseil est d'essayer la première requête et de voir si cela le rend suffisamment rapide. Sinon, implémentez les deux requêtes avec des filtres sur eux. Numériser une variable de table de rangée 845 ne prend pas de temps. Vous pourrez peut-être obtenir la meilleure des deux requêtes avec deux déclarations de mise à jour distinctes qui fonctionnent sur différentes parties de la table. La première requête peut être plus efficace lorsqu'il n'y a pas de colonne de date non nulle:

WHERE [@Auto].dtServis IS NULL AND [@Auto].dtStart IS NULL;

La deuxième requête peut être plus efficace lorsqu'il existe une colonne de date non nulle (je suppose que la colonne est quelque peu sélective):

WHERE [@Auto].dtServis IS NOT NULL OR [@Auto].dtStart IS NOT NULL
4
Joe Obbish

Je pense qu'une partie de la requête qui met à jour @Auto Table peut être insérée plus loin dans la variable du tableau.

Pour que, encore une fois, la même requête ne soit pas répétée. Elle stimulera la performance.

S'il vous plaît comprendre l'idée et faire le bon possible.

declare @table TABLE(lkmDo int, lkmOd int,IDAuto int, IDNaves int,dtZacatek datetime )
insert into @table
SELECT JizdaTachograf.lkmDo ,JizdaTachograf.lkmOd ,IDAuto,IDNaves,dtZacatek
     FROM Jizda INNER JOIN 
     JizdaTachograf ON JizdaTachograf.IDJizda = Jizda.lIDJizda 
     WHERE JizdaTachograf.lkmOd IS NOT NULL AND 
           JizdaTachograf.lkmDo IS NOT NULL AND Jizda.lProvozne = 1 
           AND (JizdaTachograf.IDAuto = [@Auto].lIDAuto 
           OR JizdaTachograf.IDNaves = [@Auto].lIDAuto) 
-- OPTION  (RECOMPILE)

    UPDATE @Auto SET lUjeto = Km.lKm
        FROM @Auto INNER JOIN 
        (SELECT
                    SUM(lkmDo - lkmOd) AS lKm, IDAuto,IDNaves
        FROM @table
        GROUP BY 
            JizdaTachograf.IDAuto,JizdaTachograf.IDNaves    
        ) as Km 
            ON Km.IDAuto = [@Auto].lIDAuto OR Km.IDNaves = [@Auto].lIDAuto



    UPDATE @Auto SET lMaxTachograf = ISNULL(ISNULL(Km.lKm, [@Auto].lKmServis),[@Auto].lKmStart)
    FROM @Auto 
         OUTER APPLY 
    (SELECT TOP 1 JizdaTachograf.lkmDo lKm 
     FROM @table
     WHERE
           Jizda.dtZacatek > ISNULL(ISNULL([@Auto].dtServis, 
           [@Auto].dtStart),DATEADD(YEAR,-100,GETDATE())) 
     ORDER BY 
           Jizda.dtZacatek DESC, JizdaTachograf.lkmDo desc) Km
0
KumarHarsh