Il tuo piano di esecuzione
Osservando il piano di query, possiamo vedere che viene toccato un indice per servire due operazioni di filtro.
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.
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:
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*"');
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:
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.
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.
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:
dove la funzione full-text viene applicata direttamente
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*"')
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: