2014-04-10 15 views
6

Mam tabeli, który wygląda tak:Potrzebujesz pomocy zrozumienie jak działają indeksy mysql

CREATE TABLE `metric` (
    `metricid` bigint(20) unsigned NOT NULL AUTO_INCREMENT, 
    `host` varchar(50) NOT NULL, 
    `userid` int(10) unsigned DEFAULT NULL, 
    `lastmetricvalue` double DEFAULT NULL, 
    `receivedat` int(10) unsigned DEFAULT NULL, 
    `name` varchar(255) NOT NULL, 
    `sampleid` tinyint(3) unsigned NOT NULL, 
    `type` tinyint(3) unsigned NOT NULL DEFAULT '0', 
    `lastrawvalue` double NOT NULL, 
    `priority` tinyint(3) unsigned NOT NULL DEFAULT '0', 
    PRIMARY KEY (`metricid`), 
    UNIQUE KEY `unique-metric` (`userid`,`host`,`name`,`sampleid`) 
) ENGINE=InnoDB AUTO_INCREMENT=1000000221496 DEFAULT CHARSET=utf8 

Ma 177,892 wierszy w tej chwili, a kiedy należy uruchomić następujące zapytanie:

select metricid, lastrawvalue, receivedat, name, sampleid 
FROM metric m 
WHERE m.userid = 8 
    AND (host, name, sampleid) IN (('localhost','0.4350799184758216cpu-3/cpu-nice',0), 
    ('localhost','0.4350799184758216cpu-3/cpu-system',0), 
    ('localhost','0.4350799184758216cpu-3/cpu-idle',0), 
    ('localhost','0.4350799184758216cpu-3/cpu-wait',0), 
    ('localhost','0.4350799184758216cpu-3/cpu-interrupt',0), 
    ('localhost','0.4350799184758216cpu-3/cpu-softirq',0), 
    ('localhost','0.4350799184758216cpu-3/cpu-steal',0), 
    ('localhost','0.4350799184758216cpu-4/cpu-user',0), 
    ('localhost','0.4350799184758216cpu-4/cpu-nice',0), 
    ('localhost','0.4350799184758216cpu-4/cpu-system',0), 
    ('localhost','0.4350799184758216cpu-4/cpu-idle',0), 
    ('localhost','0.4350799184758216cpu-4/cpu-wait',0), 
    ('localhost','0.4350799184758216cpu-4/cpu-interrupt',0), 
    ('localhost','0.4350799184758216cpu-4/cpu-softirq',0), 
    ('localhost','0.4350799184758216cpu-4/cpu-steal',0), 
    ('localhost','_util/billing-bytes',0),('localhost','_util/billing-metrics',0)); 

go trwa 0,87 sekundy, aby powrócić wyników, wyjaśnienie jest:

*************************** 1. row *************************** 
      id: 1 
    select_type: SIMPLE 
     table: m 
     type: ref 
possible_keys: unique-metric 
      key: unique-metric 
     key_len: 5 
      ref: const 
     rows: 85560 
     Extra: Using where 
1 row in set (0.00 sec) 

profil wygląda następująco:

+--------------------------------+----------+ 
| Status       | Duration | 
+--------------------------------+----------+ 
| starting      | 0.000160 | 
| checking permissions   | 0.000010 | 
| Opening tables     | 0.000021 | 
| exit open_tables()    | 0.000008 | 
| System lock     | 0.000008 | 
| mysql_lock_tables(): unlocking | 0.000005 | 
| exit mysqld_lock_tables()  | 0.000007 | 
| init       | 0.000068 | 
| optimizing      | 0.000018 | 
| statistics      | 0.000091 | 
| preparing      | 0.000042 | 
| executing      | 0.000005 | 
| Sending data     | 0.870180 | 
| innobase_commit_low():trx_comm | 0.000012 | 
| Sending data     | 0.000111 | 
| end       | 0.000009 | 
| query end      | 0.000009 | 
| ha_commit_one_phase(-1)  | 0.000015 | 
| innobase_commit_low():trx_comm | 0.000004 | 
| ha_commit_one_phase(-1)  | 0.000005 | 
| query end      | 0.000005 | 
| closing tables     | 0.000012 | 
| freeing items     | 0.000562 | 
| logging slow query    | 0.000005 | 
| cleaning up     | 0.000005 | 
| sleeping      | 0.000006 | 
+--------------------------------+----------+ 

Co wydaje mi się zbyt wysokie. Próbowałem wymienić userid = 8 and (host, name, sampleid) IN część pierwszego zapytania do (userid, host, name, sampleid) IN i ta kwerenda działa około 0.5s - prawie 2 razy szybciej, dla odniesienia, tutaj jest kwerenda:

select metricid, lastrawvalue, receivedat, name, sampleid 
FROM metric m 
WHERE (userid, host, name, sampleid) IN ((8,'localhost','0.4350799184758216cpu-3/cpu-nice',0), 
    (8,'localhost','0.4350799184758216cpu-3/cpu-system',0), 
    (8,'localhost','0.4350799184758216cpu-3/cpu-idle',0), 
    (8,'localhost','0.4350799184758216cpu-3/cpu-wait',0), 
    (8,'localhost','0.4350799184758216cpu-3/cpu-interrupt',0), 
    (8,'localhost','0.4350799184758216cpu-3/cpu-softirq',0), 
    (8,'localhost','0.4350799184758216cpu-3/cpu-steal',0), 
    (8,'localhost','0.4350799184758216cpu-4/cpu-user',0), 
    (8,'localhost','0.4350799184758216cpu-4/cpu-nice',0), 
    (8,'localhost','0.4350799184758216cpu-4/cpu-system',0), 
    (8,'localhost','0.4350799184758216cpu-4/cpu-idle',0), 
    (8,'localhost','0.4350799184758216cpu-4/cpu-wait',0), 
    (8,'localhost','0.4350799184758216cpu-4/cpu-interrupt',0), 
    (8,'localhost','0.4350799184758216cpu-4/cpu-softirq',0), 
    (8,'localhost','0.4350799184758216cpu-4/cpu-steal',0), 
    (8,'localhost','_util/billing-bytes',0), 
    (8,'localhost','_util/billing-metrics',0)); 

jej wyjaśnić wygląda następująco:

*************************** 1. row *************************** 
      id: 1 
    select_type: SIMPLE 
     table: m 
     type: ALL 
possible_keys: NULL 
      key: NULL 
     key_len: NULL 
      ref: NULL 
     rows: 171121 
     Extra: Using where 
1 row in set (0.00 sec) 

Następny zaktualizowałem tabela zawiera pojedynczą kolumnę dołączył:

alter table `metric` add `forindex` varchar(120) not null default ''; 
update metric set forindex = concat(userid,`host`,`name`,sampleid); 
alter table metric add index `forindex` (`forindex`); 

Aktualizacja zapytanie mieć tylko 1 łańcuch wyszukiwane:

select metricid, lastrawvalue, receivedat, name, sampleid 
FROM metric m 
WHERE (forindex) IN (('8localhost0.4350799184758216cpu-3/cpu-nice0'), 
    ('8localhost0.4350799184758216cpu-3/cpu-system0'), 
    ('8localhost0.4350799184758216cpu-3/cpu-idle0'), 
    ('8localhost0.4350799184758216cpu-3/cpu-wait0'), 
    ('8localhost0.4350799184758216cpu-3/cpu-interrupt0'), 
    ('8localhost0.4350799184758216cpu-3/cpu-softirq0'), 
    ('8localhost0.4350799184758216cpu-3/cpu-steal0'), 
    ('8localhost0.4350799184758216cpu-4/cpu-user0'), 
    ('8localhost0.4350799184758216cpu-4/cpu-nice0'), 
    ('8localhost0.4350799184758216cpu-4/cpu-system0'), 
    ('8localhost0.4350799184758216cpu-4/cpu-idle0'), 
    ('8localhost0.4350799184758216cpu-4/cpu-wait0'), 
    ('8localhost0.4350799184758216cpu-4/cpu-interrupt0'), 
    ('8localhost0.4350799184758216cpu-4/cpu-softirq0'), 
    ('8localhost0.4350799184758216cpu-4/cpu-steal0'), 
    ('8localhost_util/billing-bytes0'), 
    ('8localhost_util/billing-metrics0')); 

A teraz otrzymuję takie same wyniki w 0.00 sekundy! Wyjaśnić to:

*************************** 1. row *************************** 
      id: 1 
    select_type: SIMPLE 
     table: m 
     type: range 
possible_keys: forindex 
      key: forindex 
     key_len: 362 
      ref: NULL 
     rows: 17 
     Extra: Using where 
1 row in set (0.00 sec) 

więc podsumować, oto wyniki:

  1. m.userid = X AND (host, name, sampleid) IN - wskaźnik używany, 85560 wierszy skanowana, biegnie w 0.9s
  2. (userid, host, name, sampleid) IN - wskaźnik nieużywany, 171121 wiersze zeskanowane, działa w 0,5 s
  3. dodatkowa kolumna ze złożonym indeksem zastąpionym indeksem przez łączoną kolumnę użyteczności - wykorzystany indeks, 17 wierszy zeskanowanych, działa w 0 s

Dlaczego drugie zapytanie działa szybciej niż pierwsze? I dlaczego trzecie pytanie jest o wiele szybsze niż reszta? Czy powinienem zachować taką kolumnę wyłącznie w celu szybszego wyszukiwania?

wersja MySQL: mysqld Ver 5.5.34-55 for Linux on x86_64 (Percona XtraDB Cluster (GPL), wsrep_25.9.r3928)

Odpowiedz

3

Indeksy pomóc wyszukiwane w klauzuli WHERE przez zawężenie poszukiwań jak najwięcej. Możesz zobaczyć, jak to się dzieje ...

Pole EXPLAIN rows podaje szacunkową liczbę wierszy, które zapytanie będzie musiało zbadać, aby znaleźć wiersze pasujące do zapytania.Porównując rows zgłoszony w każdym EXPLAIN, można zobaczyć, jak dużo lepsza lepiej zoptymalizowane zapytania to:

 rows: 85560 -- first query 

    rows: 171121 -- second query examines 2x more rows, but it was probably 
        -- faster because the data was buffered after the first query 

    rows: 17 -- third query examines 5,000x fewer rows than first query 

będzie można również zauważyć w szczegółach Pokaż profil jeśli pobiegł że w trzecim zapytaniu, że „Wysyłanie danych "jest o wiele szybszy dla szybszego zapytania. Ten stan procesu wskazuje, ile czasu zajęło skopiowanie wierszy z silnika pamięci masowej do warstwy SQL serwera MySQL. Nawet podczas kopiowania z pamięci do pamięci zajmuje to trochę czasu dla tylu tysięcy wierszy. Właśnie dlatego indeksy są tak korzystne.

Więcej użytecznych wyjaśnień można znaleźć w mojej prezentacji How to Design Indexes, Really.

+0

Przeprowadziłem już pierwsze kwerendy wiele razy z wynikami w tym samym czasie, więc dane również zostały zbuforowane. Skończyło się na użyciu wielu instrukcji '(x i x) lub (x i x)', ale chodzi o to, dlaczego nie "gdzie (a, b) w ((x1, x2), (x3, x4)) 'działa? – Fluffy

+0

Dla drugiej wydajności zapytania, czy to możliwe, że wyszukiwanie rekordów 85560 (z drugorzędnego do klastrowego indeksu) było wolniejsze niż zwykłe skanowanie za pomocą indeksu klastrowego? –

+0

@Fluffy, nie jestem pewien, może to być po prostu, że składnia nie jest zoptymalizowana. –