web-dev-qa-db-fra.com

Fusionner le syntonisation de la performance

J'ai examiné un problème avec des performances avec une requête SQL de fournisseur, généralement avec ce fournisseur, je peux voir des index qui amélioreront les performances, mais cette requête est actuellement un peu hors de ma ligue en termes de réglage des performances. Espérons que, après cette demande, je vais apprendre quelque chose de nouveau.

Le problème avec le SQL semble être une jointure de fusion (jointure intérieure) et je ne suis pas sûr de la meilleure façon d'essayer d'améliorer la performance de ceux-ci.

J'apprécierais vraiment que quelqu'un me pointe dans la bonne direction ou s'il y a plus d'informations que je peux fournir, alors faites-le-moi savoir.

Voici le plan d'exécution et la requête SQL .

Il n'y a pas beaucoup d'index sur celui-ci, car j'ai essayé d'appliquer des index, mais ils ne semblent avoir aucun effet réduisant la jointure de fusion. Par conséquent, le plan que je vous ai donné utilise les index par défaut du fournisseur.

Mise à jour 1: J'ai ajouté des index à la requête, mais ils ont peu d'incidence, voici les index et le nouveau plan

nouveau plan SQL et les index

J'ai également mis à jour les statistiques sur toutes les tables impliquées dans la requête.

L'utilisation de l'option (jointure de hachage) réduit le temps d'exécution de 30 secondes à 15 secondes. Cependant, c'est le fournisseur SQL exécuté de derrière un écran afin que je ne puisse pas forcer ceci.

MaxDop sur le serveur est défini sur 0 et le seuil de coûts est 50, donc pas sûr pourquoi il ne veut pas aller parallèlement.

Mise à jour 2: Il semble que les index uniques suivants sur la table ATSSEHOURDETTMAP provoquent le problème. Si je dépose ces points, la requête fonctionne en moins d'une seconde.

Créez un index unique [AIATSSETHOURDETTMAP2] sur [DBO]. [AtstehourdeTTMAP] ([OID]);

Créer un index unique [AIATSSETEHOURDETTMAP1] sur [DBO]. [AtstehourdetTamap] ([Tsehourdet_id]);

Des idées pourquoi?

3
David Barrett

Le problème avec le SQL semble être une fusion rejoindre ...

L'estimation de l'optimiseur est que la jointure de fusion représentera 85,8% du coût d'exécution de la requête, mais vous devez toujours traiter ces chiffres avec un degré de suspicion sain.

Ce ne sont que des estimations basées sur un modèle de coûts assez simple qui aide l'optimiseur à faire des choix généraux entre les solutions de remplacement, mais les chiffres ne sont certainement pas certainement ne pas corréler avec les caractéristiques de performance du plan d'exécution sur votre matériel particulier. Les estimations des coûts sont un indice large à des choses que l'optimiseur ressentie était coûteuse, mais il n'y a pas de substitut à l'analyse par un professionnel de la base de données expérimenté et expérimenté.

Cela dit, la principale raison de l'estimation élevée des coûts de l'optimiseur pour la fusion se joint à ce cas est que la jointure est supposée être nombreuses à plusieurs. En effet, le schéma de base de données ne fournit aucune contrainte ni index uniques pour garantir une relation une à plusieurs. Les jointures assumées à plusieurs à plusieurs sont également la raison de l'estimation de la cardinalité de la cardonnée de la sortie de la sortie. Étant donné des informations incomplètes, l'optimiseur produit une estimation inexacte.

La question ne fournit pas de schéma DDL pour les tableaux impliqués, mais nous pouvons déduire certaines des colonnes du plan d'exécution et quelque chose sur les relations des jointures. Les types de données sont inconnus pour la plupart et nous ne pouvons pas savoir sur des clés étrangères ou d'autres contraintes, bien sûr.

Une mise à jour de la question pendant que j'écrivais cette réponse stipule que la requête ne peut pas être modifiée, mais pour l'analyse, je l'avais déjà réécrit pour utiliser au moins une syntaxe moderne JOIN:

SELECT
    a.wf_state, 
    b.oid, 
    p.wf_user_id, 
    p.error_no, 
    item_followup = 1,
    p.element_type, 
    selected = 0,
    p.version_no, 
    p.p_description, 
    p.s_description, 
    p.proc_node_id, 
    p.step_node_id, 
    p.distr_type, 
    f.project, 
    d.reg_period, 
    d.resource_id, 
    d.last_update, 
    a.used_hrs 
FROM dbo.awfenquiry AS p
JOIN dbo.atstsehourdetmap AS b
    ON b.oid = p.oid
JOIN dbo.atstsehourdet AS a
    ON a.tsehourdet_id = b.tsehourdet_id
JOIN dbo.atstsegldetail AS c
    ON c.tsegldetail_id = a.tsegldetail_id
JOIN dbo.atstseheader AS d
    ON d.tseheader_id = c.tseheader_id
JOIN dbo.atschargecode AS e
    ON e.client = c.client
    AND e.chargecode_id = c.chargecode_id
JOIN dbo.atsproject AS f
    ON f.project = e.project
    AND f.client = e.client
WHERE
    p.element_type = N'TS' 
    AND a.client = N'GB' 
    AND 
    (
        a.wf_state = N'U' 
        OR p.distr_type = N'U' 
    );

L'une des principales améliorations que vous pouvez faire est de donner à chaque table un index en cluster. Presque toutes les tableaux bénéficient d'un indice en cluster, soit pour des raisons de gestion de l'espace, ou simplement parce qu'il fournit un index "gratuit" supplémentaire. Dans de nombreux cas, l'indice en cluster sera également la clé principale, bien que cela ne soit pas nécessaire. Toutes les tables doivent avoir une clé. Beaucoup de gens diraient qu'une table est incorrectement formée sans un. En tant que point de départ, j'ai donné à chaque table une clé primaire basée sur ma meilleure hypothèse. La clé principale est regroupée par défaut.

Mettre tout cela ensemble, un premier passage à un schéma est:

CREATE TABLE dbo.awfenquiry -- p
(
    oid integer PRIMARY KEY,
    element_type nvarchar(50) NULL,
    wf_user_id integer NULL,
    error_no integer NULL,
    version_no integer NULL,
    p_description nvarchar(50) NULL,
    s_description nvarchar(50) NULL,
    proc_node_id integer NULL,
    step_node_id integer NULL,
    distr_type nvarchar(10) NULL
);

CREATE TABLE dbo.atstsehourdetmap -- b
(
    oid integer NOT NULL,
    tsehourdet_id integer NOT NULL,

    PRIMARY KEY (oid, tsehourdet_id)
);

CREATE TABLE dbo.atstsehourdet -- a
(
    tsehourdet_id integer PRIMARY KEY,
    tsegldetail_id integer NULL,
    wf_state nvarchar(10) NULL,
    used_hrs integer NULL,
    client nvarchar(10) NULL
);

CREATE TABLE dbo.atstsegldetail -- c
(
    tsegldetail_id integer PRIMARY KEY,
    tseheader_id integer NULL,
    client nvarchar(10) NULL,
    chargecode_id integer NULL
);

CREATE TABLE dbo.atstseheader -- d
(
    tseheader_id integer PRIMARY KEY,
    reg_period integer NULL, 
    resource_id integer NULL, 
    last_update datetime2 NULL, 
);

CREATE TABLE dbo.atschargecode -- e
(
    chargecode_id integer NOT NULL,
    client nvarchar(10) NOT NULL,
    project integer NULL,

    PRIMARY KEY (chargecode_id, client)
);

CREATE TABLE dbo.atsproject -- f
(
    project integer NOT NULL,
    client nvarchar(10) NOT NULL,

    PRIMARY KEY (project, client)
);

Les statistiques n'ont pas été fournies non plus, mais nous pouvons au moins voir les cardinalités de la table globales dans le plan d'exécution. Les affirmations suivantes fournissent ces informations pour les définitions de tableau ci-dessus, sans aucune information sur la distribution de la valeur:

UPDATE STATISTICS dbo.awfenquiry WITH ROWCOUNT = 51826;
UPDATE STATISTICS dbo.atstsehourdetmap WITH ROWCOUNT = 2748620;
UPDATE STATISTICS dbo.atstsehourdet WITH ROWCOUNT = 2743040;
UPDATE STATISTICS dbo.atstsegldetail WITH ROWCOUNT = 1223270;
UPDATE STATISTICS dbo.atstseheader WITH ROWCOUNT = 328822;
UPDATE STATISTICS dbo.atschargecode WITH ROWCOUNT = 23983;
UPDATE STATISTICS dbo.atsproject WITH ROWCOUNT = 18169;

Basé sur la requête, il y a peu de candidats index évidents au-delà des clés déjà ajoutées. Deux qui peuvent être utiles sont:

CREATE NONCLUSTERED INDEX i 
ON dbo.awfenquiry (element_type);

CREATE NONCLUSTERED INDEX i 
ON dbo.atstsehourdet (client) 
INCLUDE (wf_state, tsegldetail_id, used_hrs);

Le plan d'exécution résultant n'est évidemment pas une amélioration massive sur l'original, à part le (maintenant un à plusieurs) rejoignez, mais c'est une étape dans la bonne direction peut-être:

enter image description here

Toutes les tables sont relativement petites, alors je vérifierais probablement à côté de voir si l'exécution de la requête est bloquée par un autre processus, de la lutte contre des ressources avec d'autres requêtes ou de la lutte avec un système de stockage accablé. Quinze secondes semble entièrement déraisonnable pour la tâche.

8
Paul White 9