Interrogazione lenta quando si hanno "contiene" e "=" insieme nella clausola where


8

La seguente query richiede circa 10 secondi per terminare su una tabella con record 12k

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

Ma se cambio la clausola where in entrambi

where "id" = 1

o

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

Tornerà all'istante.

Entrambe le colonne sono indicizzate e anche la colonna lastName è indicizzata full-text.

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;

Ecco il piano di esecuzione

Quale potrebbe essere il problema?


Ho appena aggiunto la definizione della tabella
Hooman Valibeigi,

Risposte:


11

Il tuo piano di esecuzione

Osservando il piano di query, possiamo vedere che viene toccato un indice per servire due operazioni di filtro.

inserisci qui la descrizione dell'immagine

In parole povere, grazie all'operatore TOP, è stato fissato un obiettivo di fila. Molte più informazioni e prerequisiti sugli obiettivi di fila sono disponibili qui

Dalla stessa fonte:

Una strategia per obiettivi di riga in genere significa favorire operazioni di navigazione non bloccanti (ad esempio join di loop nidificati, ricerche di indici e ricerche) rispetto a operazioni di blocco, basate su set come ordinamento e hash. Questo può essere utile ogni volta che il client può beneficiare di un avvio rapido e di un flusso costante di righe (con forse un tempo di esecuzione complessivo più lungo - vedi il post di Rob Farley sopra). Ci sono anche gli usi più ovvi e tradizionali, ad esempio nel presentare i risultati una pagina alla volta.

L'intera tabella viene sondata nei filtri con l'uso di un semi join sinistro con un obiettivo prefissato impostato, nella speranza di restituire le 5 righe nel modo più rapido ed efficiente possibile.

Ciò non accade, causando molte iterazioni su .Fulltextmatch TVF.

inserisci qui la descrizione dell'immagine


ricreare

Sulla base del tuo piano , sono stato in grado di ricreare in qualche modo il tuo problema:

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

Esecuzione della query

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

Risultati in un piano di query paragonabile al tuo:

inserisci qui la descrizione dell'immagine

Nell'esempio sopra, B non esiste nell'indice full-text. Di conseguenza dipende dal parametro e dai dati quanto efficiente può essere il piano di query.

Una spiegazione migliore di ciò può essere trovata in Row Goals, Part 2: Semi Joins di Paul White

... In altre parole, ad ogni iterazione di una domanda, possiamo smettere di guardare l'input B non appena viene trovata la prima corrispondenza, usando il predicato di join push-down. Questo è esattamente il genere di cosa per cui un obiettivo di riga è utile: generare parte di un piano ottimizzato per restituire rapidamente le prime n righe corrispondenti (dove n = 1 qui).

Ad esempio, cambiando il predicato in modo che i risultati vengano trovati molto prima (all'inizio della scansione).

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

inserisci qui la descrizione dell'immagine

il where "id" = 124'eliminato grazie al predicato dell'indice documento già ritorno 5 righe, soddisfacendo il TOP()predicato.

I risultati mostrano anche questo

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

E le esecuzioni TVF:

inserisci qui la descrizione dell'immagine

Inserimento di alcune nuove righe

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

Esecuzione della query per trovare queste precedenti righe inserite

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

Ciò si traduce nuovamente in troppe iterazioni su quasi tutte le righe per restituire l'ultimo ma un valore trovato.

inserisci qui la descrizione dell'immagine

inserisci qui la descrizione dell'immagine

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

Risoluzione

Quando si rimuove l'obiettivo di fila usando traceflag 4138

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

L'ottimizzatore utilizza un modello di join più vicino all'implementazione di un UNION, nel nostro caso questo è favorevole in quanto spinge i predicati verso il basso nelle rispettive ricerche dell'indice cluster e non utilizza l'operatore di semi-join a sinistra con obiettivo con riga.

inserisci qui la descrizione dell'immagine

Un altro modo per scrivere questo, senza usare il traceflag sopra menzionato:

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

Con il piano di query risultante:

inserisci qui la descrizione dell'immagine

dove la funzione full-text viene applicata direttamente

inserisci qui la descrizione dell'immagine

Come sidenote, per op, l'hotfix 4199 traceflag dell'ottimizzatore delle query ha risolto il suo problema. Lo ha implementato aggiungendo OPTION(QUERYTRACEON(4199))alla query. Non sono stato in grado di riprodurre quel comportamento da parte mia. Questo aggiornamento rapido contiene un'ottimizzazione semi join:

Flag di traccia: 4102 Funzione: SQL 9 - Le prestazioni della query sono lente se il piano di esecuzione della query contiene operatori semi join In genere, gli operatori semi join vengono generati quando la query contiene la parola chiave IN o la parola chiave EXISTS. Abilita i flag 4102 e 4118 per ovviare a questo.

fonte


Extra

Durante l'ottimizzazione basata sui costi, l'ottimizzatore potrebbe anche aggiungere uno spool di indice al piano di esecuzione, implementato da LogOp_Spool Index on fly Eager (o dalla controparte fisica)

Lo fa con il mio set di dati per TOP(3)ma non perTOP(2)

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

inserisci qui la descrizione dell'immagine

Alla prima esecuzione, uno spool desideroso legge e memorizza l'intero input prima di restituire il sottoinsieme di righe richiesto dalle esecuzioni Predicate Later leggi e restituisce lo stesso o un diverso sottoinsieme di righe dal piano di lavoro, senza mai dover eseguire il figlio di nuovo nodi.

fonte

Con il predicato di ricerca applicato a questo spooler desideroso di indice:

inserisci qui la descrizione dell'immagine


Potresti forse spiegare l'uso delle Bandiere Traccia? Dal tuo codice non è chiaro cosa stiano facendo
George.Palacios,

1
@ George.Palacios Sì, ho fatto un po 'di casino: ^). Farò grazie per il feedback!
Randi Vertongen,

Nessuno dei flag QUERYTRACEON suggeriti (4138, 3604, 8607, 8612) ha funzionato, ma QUERYTRACEON 4199 sembra risolvere il problema !!!!
Hooman Valibeigi,

Si noti che la query è lenta anche senza l'operatore TOP
Hooman Valibeigi,

@HoomanValibeigi hai provato la soluzione sindacale in fondo?
Randi Vertongen,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.