2013-09-04 12 views
5

uproszczony, mam dwie tabele, contacts i donotcallWykorzystaj indeksu kiedy JOIN'ing przeciwko wielu kolumn

CREATE TABLE contacts 
(
    id int PRIMARY KEY, 
    phone1 varchar(20) NULL, 
    phone2 varchar(20) NULL, 
    phone3 varchar(20) NULL, 
    phone4 varchar(20) NULL 
); 
CREATE TABLE donotcall 
(
    list_id int NOT NULL, 
    phone varchar(20) NOT NULL 
); 
CREATE NONCLUSTERED INDEX IX_donotcall_list_phone ON donotcall 
(
    list_id ASC, 
    phone ASC 
); 

chciałbym zobaczyć, co pasuje do kontaktów numer telefonu w specjalnym liście DoNotCall telefonu. Dla szybszego wyszukiwania, zaindeksowałem donotcall na list_id i phone.

Kiedy dokonać następujących DOŁĄCZ to zajmuje dużo czasu (na przykład 9 sekund.):

SELECT DISTINCT c.id 
FROM contacts c 
JOIN donotcall d 
    ON d.list_id = 1 
    AND d.phone IN (c.phone1, c.phone2, c.phone3, c.phone4) 

Screenshot of execution plan

Execution plan on Pastebin

Chociaż gdybym LEFT JOIN na każdym polu telefonu osobno go działa znacznie szybciej (np. 1,5 sekundy):

SELECT c.id 
FROM contacts c 
LEFT JOIN donotcall d1 
    ON d1.list_id = 1 
    AND d1.phone = c.phone1 
LEFT JOIN donotcall d2 
    ON d2.list_id = 1 
    AND d2.phone = c.phone2 
LEFT JOIN donotcall d3 
    ON d3.list_id = 1 
    AND d3.phone = c.phone3 
LEFT JOIN donotcall d4 
    ON d4.list_id = 1 
    AND d4.phone = c.phone4 
WHERE 
    d1.phone IS NOT NULL 
    OR d2.phone IS NOT NULL 
    OR d3.phone IS NOT NULL 
    OR d4.phone IS NOT NULL 

Screenshot of execution plan

Execution plan on Pastebin

Moje założenie jest, że pierwszy fragment przebiega powoli, ponieważ nie wykorzystują indeks na donotcall.
A więc, jak wykonać sprzężenie w kierunku wielu kolumn i nadal korzystać z indeksu?

+0

Naprawdę musisz naprawić strukturę bazy danych. NIGDY nie powinieneś mieć telefonu1, telefonu2, telefonu3, telefonu4 - który wskazuje, że potrzebujesz dziecięcego stołu. – HLGEM

+0

@HLGEM: Punkt oznaczony. Zrobiłbym to inaczej, gdybym miał wybór. Ale czasami utkniesz w strukturze, którą stworzyli inni, bez nadziei na refaktoryzację. – ANisus

Odpowiedz

6

SQL Server może pomyśleć, że rozwiązanie IN (c.phone1, c.phone2, c.phone3, c.phone4) za pomocą indeksu jest zbyt kosztowne.

Można sprawdzić, czy indeks będzie szybciej z nutą:

SELECT c.* 
FROM contacts c 
JOIN donotcall d with (index(IX_donotcall_list_phone)) 
    ON d.list_id = 1 
    AND d.phone IN (c.phone1, c.phone2, c.phone3, c.phone4) 

z kwerendy planuje pan pisał, to pokazuje pierwszy plan Szacuje się produkować 40K wierszy, ale to po prostu zwraca 21 wierszy. Drugi plan szacuje 1 wiersz (i oczywiście zwraca także 21).

Czy Twoja statistics jest aktualna? Nieaktualne statystyki mogą wyjaśnić, że analizator zapytań dokonuje złych wyborów. Statystyki powinny być aktualizowane automatycznie lub co tydzień. Sprawdzić wiek statystyk z:

select object_name(ind.object_id) as TableName 
,  ind.name as IndexName 
,  stats_date(ind.object_id, ind.index_id) as StatisticsDate 
from sys.indexes ind 
order by 
     stats_date(ind.object_id, ind.index_id) desc 

Można update them ręcznie z:

EXEC sp_updatestats; 
+0

Dobra sugestia. Próbowałem dodać podpowiedź. Jednak nic to nie zmieniło. Wersja LEFT JOIN jest wciąż dużo szybsza. Wygląda na to, że serwer SQL nie jest w stanie rozwiązać kodu przy użyciu indeksu. – ANisus

+2

Czy próbowałeś porównać plany zapytania? (Zapytanie menu, a następnie uwzględnij rzeczywisty plan wykonania). – Andomar

+0

Próbuję to zrobić, ale mam bardzo mało doświadczenia w analizowaniu planów zapytań.Z tego co widzę, rozwiązanie LEFT JOIN jest dość proste, używając 4 dopasowań Hash (Right Outer Joins). Pojedyncze rozwiązanie JOIN robi to zupełnie inaczej. Dwie zagnieżdżone pętle (Wewnętrzne połączenia), najpierw z indeksem, a następnie z tabelą kontaktów. – ANisus

0

Dzięki tej słabej struktury bazy danych, UNION ALL zapytanie może być najszybszy.

+0

Podczas gdy UNION ALL obecnie działa lepiej niż pojedyncze rozwiązanie JOIN, jest nieco wolniejsze niż rozwiązanie LEFT JOIN. – ANisus

Powiązane problemy