Requête lente lorsque la clause «contient» et «=» ensemble dans la clause where

8

La requête suivante prend environ 10 secondes pour terminer sur une table avec 12k enregistrements

select top (5) *
from "Physician"
where "id" = 1 or contains("lastName", '"a*"')

Mais si je change la clause where en

where "id" = 1

ou

where contains("lastName", '"a*"')

Il reviendra instantanément.

Les deux colonnes sont indexées et la colonne lastName est également indexée en texte intégral.

CREATE TABLE Physician
(
   id         int identity    NOT NULL,
   firstName  nvarchar(100)   NOT NULL,
   lastName   nvarchar(100)   NOT NULL
);

ALTER TABLE Physician
  ADD CONSTRAINT Physician_PK
  PRIMARY KEY CLUSTERED (id);

CREATE NONCLUSTERED INDEX Physician_IX2
   ON Physician (firstName ASC);

CREATE NONCLUSTERED INDEX Physician_IX3
   ON Physician (lastName ASC);

CREATE FULLTEXT INDEX
    ON "Physician" ("firstName" LANGUAGE 0x0, "lastName" LANGUAGE 0x0)
    KEY INDEX "Physician_PK"
    ON "the_catalog"
    WITH stoplist = off;

Voici le plan d'exécution

Quel pourrait être le problème?

Hooman Valibeigi
la source
Je viens d'ajouter la définition de la table
Hooman Valibeigi

Réponses:

11

Votre plan d'exécution

En regardant le plan de requête, nous pouvons voir qu'un index est touché pour servir deux opérations de filtrage.

entrez la description de l'image ici

En termes très simples, en raison de l'opérateur TOP, un objectif de ligne a été fixé. De plus amples informations et conditions préalables sur les objectifs de rang peuvent être trouvées ici

De cette même source:

Une stratégie d'objectif de ligne signifie généralement privilégier les opérations de navigation non bloquantes (par exemple, les jointures de boucles imbriquées, les recherches d'index et les recherches) par rapport aux opérations de blocage basées sur des ensembles comme le tri et le hachage. Cela peut être utile chaque fois que le client peut bénéficier d'un démarrage rapide et d'un flux régulier de lignes (avec peut-être un temps d'exécution global plus long - voir le post de Rob Farley ci-dessus). Il y a aussi les utilisations les plus évidentes et traditionnelles, par exemple pour présenter les résultats une page à la fois.

La table entière est sondée dans les filtres à l'aide d'une semi-jointure gauche qui a un objectif de ligne défini, dans l'espoir de renvoyer les 5 lignes aussi rapidement et efficacement que possible.

Cela ne se produit pas, ce qui entraîne de nombreuses itérations sur le .Fulltextmatch TVF.

entrez la description de l'image ici


Recréer

Sur la base de votre plan , j'ai pu recréer quelque peu votre problème:

CREATE TABLE dbo.Person(id int not null,lastname varchar(max));

CREATE UNIQUE INDEX ui_id ON  dbo.Person(id)
CREATE FULLTEXT CATALOG ft AS DEFAULT;  
CREATE FULLTEXT INDEX ON dbo.Person(lastname)   
   KEY INDEX ui_id   
   WITH STOPLIST = SYSTEM;  
GO  

INSERT INTO dbo.Person(id,lastname)
SELECT top(12000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)),
REPLICATE(CAST('A' as nvarchar(max)),80000)+ CAST(ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) as varchar(10))
FROM master..spt_values spt1
CROSS APPLY master..spt_values spt2;
CREATE CLUSTERED INDEX cx_Id on dbo.Person(id);

Exécution de la requête

SELECT TOP (5) *
FROM dbo.Person
WHERE "id" = 1 OR contains("lastName", '"B*"');

Résultats dans un plan de requête comparable au vôtre:

entrez la description de l'image ici

Dans l'exemple ci-dessus, B n'existe pas dans l'index de texte intégral. En conséquence, cela dépend du paramètre et des données de l'efficacité du plan de requête.

Une meilleure explication de cela peut être trouvée dans Row Goals, Part 2: Semi Joins par Paul White

... En d'autres termes, à chaque itération d'une application, nous pouvons arrêter de regarder l'entrée B dès que la première correspondance est trouvée, en utilisant le prédicat de jointure poussé. C'est exactement le genre de chose pour laquelle un objectif de ligne est bon: générer une partie d'un plan optimisé pour renvoyer rapidement les n premières lignes correspondantes (où n = 1 ici).

Par exemple, changer le prédicat pour que les résultats soient trouvés bien plus tôt (au début de l'analyse).

select top (5) *
from dbo.Person
where "id" = 124 
or contains("lastName", '"A*"');

entrez la description de l'image ici

le where "id" = 124est éliminé en raison du prédicat d'index de texte intégral renvoyant déjà 5 lignes, satisfaisant le TOP()prédicat.

Les résultats le montrent également

id lastname 
1  'AAA...'   
2  'AAA...'
3  'AAA...'
4  'AAA...'
5  'AAA...'

Et les exécutions de TVF:

entrez la description de l'image ici

Insertion de nouvelles lignes

INSERT INTO dbo.Person
SELECT 12001, REPLICATE(CAST('B' as nvarchar(max)),80000);
INSERT INTO dbo.Person
SELECT 12002, REPLICATE(CAST('B' as nvarchar(max)),80000);

Exécution de la requête pour rechercher ces lignes insérées précédentes

SELECT TOP (2) *
from dbo.Person
where "id" = 1
or contains("lastName", '"B*"');

Cela entraîne à nouveau trop d'itérations sur presque toutes les lignes pour renvoyer la dernière valeur trouvée.

entrez la description de l'image ici

entrez la description de l'image ici

id   lastname
1     'AAA...'
12001 'BBB...'

Résolution

Lors de la suppression de l'objectif de ligne à l'aide de traceflag 4138

SELECT TOP (5) *
FROM dbo.Person
WHERE "id" = 124 
OR contains("lastName", '"B*"')
OPTION(QUERYTRACEON 4138 );

L'optimiseur utilise un modèle de jointure plus proche de l'implémentation de a UNION, dans notre cas, cela est favorable car il pousse les prédicats vers le bas vers leurs recherches d'index cluster respectives, et n'utilise pas l'opérateur de demi-jointure gauche ciblé par ligne.

entrez la description de l'image ici

Une autre façon d'écrire ceci, sans utiliser le traceflag mentionné ci-dessus:

SELECT top (5) *
FROM
(
SELECT * 
FROM dbo.Person
WHERE "id" = 1 
UNION
SELECT * 
FROM dbo.Person
WHERE contains("lastName", '"B*"')
 ) as A;

Avec le plan de requête résultant:

entrez la description de l'image ici

où la fonction de texte intégral est appliquée directement

entrez la description de l'image ici

En guise de note, pour op, le correctif de l'optimiseur de requêtes traceflag 4199 a résolu son problème. Il a implémenté cela en ajoutant OPTION(QUERYTRACEON(4199))à la requête. Je n'ai pas pu reproduire ce comportement de mon côté. Ce correctif contient une optimisation de semi-jointure:

Indicateur de trace: 4102 Fonction: SQL 9 - Les performances de la requête sont lentes si le plan d'exécution de la requête contient des opérateurs de semi-jointure En règle générale, les opérateurs de semi-jointure sont générés lorsque la requête contient le mot clé IN ou le mot clé EXISTS. Activez les indicateurs 4102 et 4118 pour résoudre ce problème.

La source


Supplémentaire

Pendant l'optimisation basée sur les coûts, l'optimiseur peut également ajouter une bobine d'index au plan d'exécution, implémentée par LogOp_Spool Index on fly Eager (ou l'homologue physique)

Il le fait avec mon jeu de données pour TOP(3)mais pas pourTOP(2)

SELECT TOP (3) *
from dbo.Physician
where "id" = 1
or contains("lastName", '"B*"')  

entrez la description de l'image ici

Lors de la première exécution, un spool impatient lit et stocke l'intégralité de l'entrée avant de renvoyer le sous-ensemble de lignes qui est demandé par les exécutions Predicate Later lire et renvoyer le même ou un sous-ensemble différent de lignes de la table de travail, sans jamais avoir à exécuter l'enfant nœuds à nouveau.

La source

Avec le prédicat de recherche appliqué à cette bobine impatiente d'index:

entrez la description de l'image ici

Randi Vertongen
la source
Pourriez-vous éventuellement expliquer l'utilisation des indicateurs de trace? Il n'est pas clair dans votre code ce qu'ils font
George.Palacios
1
@ George.Palacios Oui, j'ai fait un peu de désordre: ^). Fera merci pour les commentaires!
Randi Vertongen
Aucun des indicateurs QUERYTRACEON suggérés (4138, 3604, 8607, 8612) n'a fonctionné, mais QUERYTRACEON 4199 résout le problème !!!!
Hooman Valibeigi
Notez que la requête est lente même sans l'opérateur TOP
Hooman Valibeigi
@HoomanValibeigi avez-vous essayé la solution syndicale au fond?
Randi Vertongen