2016-07-07 2 views
0

Несколько раз у меня была такая ситуация: запрос, который работал быстро, начал работать в 1000-10_000 раз медленнее за один момент, когда не было никаких изменений. MySQL перестает использовать правильный индекс, и мне нужно использовать FORCE INDEX(..). Это происходит с запросами к большим таблицам с записями 10-300M.Иногда MySQL перестает использовать правый индекс

MySQL: 5.6.23 (AWS RDS, db.r3.xlarge)

Существует последний вопрос:

table1 (175м записей)

CREATE TABLE `table1` (
    `id` int(11) NOT NULL AUTO_INCREMENT, 
    `site_id` int(11) NOT NULL, 
    `created_at` datetime DEFAULT NULL, 
    `type` varchar(25) DEFAULT NULL, 
    ... 
    PRIMARY KEY (`id`), 
    UNIQUE KEY `index_table1_on_site_id_and_..._and_type_and_...` (`site_id`,`...`,`type`,`...`), 
    KEY `index_table1_on_created_at_and_site_id` (`created_at`,`site_id`), 
    KEY `index_table1_on_site_id_and_type_and_created_at_and_...` (`site_id`,`type`,`created_at`,`...`) USING BTREE, 
    KEY `index_table1_on_site_and_type_and_..._and_created` (`site_id`,`type`,`..._id`,`created_at`), 
) ENGINE=InnoDB AUTO_INCREMENT=... DEFAULT CHARSET=utf8 

table2 (2M записей)

CREATE TABLE `table2` (
    `id` int(11) NOT NULL AUTO_INCREMENT, 
    `table1_id` int(11) NOT NULL, 
    ... 
    PRIMARY KEY (`id`), 
    ... 
) ENGINE=InnoDB AUTO_INCREMENT=... DEFAULT CHARSET=utf8 

запрос:

SELECT `table1`.* FROM `table1` 
INNER JOIN `table2` ON `table2`.`table1_id` = `table1`.`id` 
WHERE `table1`.`type` IN ('...', '...') 
    AND `table1`.`site_id` = ... 
    AND (table1.created_at >= '...') 
    AND (table1.created_at <= '...') 
ORDER BY `table1`.`id` DESC LIMIT 30 offset 0; 

был ~ 10-80ms теперь> 420 сек

запрос с FORCE INDEX:

SELECT `table1`.* FROM `table1` USE INDEX (`index_table1_on_site_id_and_type_and_created_at_and_...`) 
INNER JOIN `table2` ON `table2`.`table1_id` = `table1`.`id` 
WHERE `table1`.`type` IN ('...', '...') 
    AND `table1`.`site_id` = ... 
    AND (table1.created_at >= '...') 
    AND (table1.created_at <= '...') 
ORDER BY `table1`.`id` DESC LIMIT 30 offset 0; 

~ 85 мс

EXPLAINE: без FORCE

*************************** 1. row *************************** 
      id: 1 
    select_type: SIMPLE 
     table: table1 
     type: index 
possible_keys: PRIMARY,index_table1_on_site_id_and_..._and_type_and_...,index_table1_on_created_at_and_site_id,index_table1_on_type,index_table1_on_site_id_and_type_and_created_at_and_...,index_table1_on_site_and_type_and_..._and_created 
      key: PRIMARY 
     key_len: 4 
      ref: NULL 
     rows: 9257179 
     Extra: Using where 
*************************** 2. row *************************** 
      id: 1 
    select_type: SIMPLE 
     table: table2 
     type: eq_ref 
possible_keys: ... 
      key: ... 
     key_len: 4 
      ref: db.table1.id 
     rows: 1 
     Extra: Using index 

с FORCE

*************************** 1. row *************************** 
      id: 1 
    select_type: SIMPLE 
     table: table1 
     type: range 
possible_keys: index_table1_on_site_id_and_type_and_created_at_and_... 
      key: index_table1_on_site_id_and_type_and_created_at_and_... 
     key_len: 88 
      ref: NULL 
     rows: 499 
     Extra: Using index condition; Using filesort 
*************************** 2. row *************************** 
      id: 1 
    select_type: SIMPLE 
     table: table2 
     type: eq_ref 
possible_keys: ... 
      key: ... 
     key_len: 4 
      ref: db.table1.id 
     rows: 1 
     Extra: Using index 

Есть ли какие-либо решения, позволяющие избежать такого непредсказуемого поведения MySQL? Я не могу добавить FORCE INDEX ко всем запросам, что делать?

PS:

SELECT * FROM `table1` 
INNER JOIN `table2` ON `table2`.`table1_id` = `table1`.`id` 
WHERE `table1`.`site_id` = ... ; 

возвращает только 122 записей

PSS: Сумасшедший, но запрос работает быстрее периода шире времени

AND (table1.created_at >= '2016-07-01') AND (table1.created_at <= '2016-07-07) 

420 сек

AND (table1.created_at >= '2016-06-01') AND (table1.created_at <= '2016-07-07)  

85ms

+0

mysql может решить, что полное сканирование таблицы будет более эффективным, чем использование индекса. если он решит это, тогда вы можете только переписать запрос, чтобы обмануть его в мышление иначе или форсировать индекс. –

+0

- странно, что MySQL может предпочесть сканировать 9M строк вместо 499 – Alexey

+0

- страшно, что MySQL может работать с индексами правильно и в какой-то момент решает больше не использовать индекс – Alexey

ответ

0

Если таблицы были изменены, вы можете попробовать запустить ANALYZE TABLE (http://dev.mysql.com/doc/refman/5.7/en/analyze-table.html) для синхронного обновления статистики. InnoDB persists optimizer stats, который имеет limitations.

На основе диапазонов дат, мне тоже интересно, если это было бы так же быстро, если вы сделали

AND (table1.created_at >= '2016-06-01') AND (table1.created_at <= '2016-06-07)' 

предполагая, что старые данные имеют более стабильную статистику, и это не размер, что делает разницу.

+0

Таблицы не были изменены – Alexey

+0

Я запускаю 'ANALYZE TABLE', но он не помог – Alexey

+0

Интересно, запрос с' created_at> = '2016-06-01' AND created_at <= '2016-06-07'' и 'created_at > = '2016-07-01' AND created_at <= '2016-07-07'' работает медленно, но с 'created_at> =' 2016-06-01 'AND created_at <=' 2016-07-07'' работает быстро – Alexey