2013-12-17 3 views
1

У меня есть прямая прямая таблица, которая в настоящее время имеет ~ 10M строк. Вот определение:Почему MySQL не использует индекс из EXPLAIN?

CREATE TABLE `train_run_messages` (
    `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 
    `train_id` int(10) unsigned NOT NULL, 
    `customer_id` int(10) unsigned NOT NULL, 
    `station_id` int(10) unsigned NOT NULL, 
    `train_run_id` int(10) unsigned NOT NULL, 
    `timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 
    `type` tinyint(4) NOT NULL, 
    `customer_station_track_id` int(10) unsigned DEFAULT NULL, 
    `lateness_type` tinyint(3) unsigned NOT NULL, 
    `lateness_amount` mediumint(9) NOT NULL, 
    `lateness_code` tinyint(3) unsigned DEFAULT '0', 
    `info_text` varchar(32) DEFAULT NULL, 
    PRIMARY KEY (`id`), 
    KEY `timestamp` (`timestamp`), 
    KEY `lateness_amount` (`lateness_amount`), 
    KEY `customer_timestamp` (`customer_id`,`timestamp`), 
    KEY `trm_customer` (`customer_id`), 
    KEY `trm_train` (`train_id`), 
    KEY `trm_station` (`station_id`), 
    KEY `trm_trainrun` (`train_run_id`), 
    KEY `FI_trm_customer_station_tracks` (`customer_station_track_id`), 
    CONSTRAINT `FK_trm_customer_station_tracks` FOREIGN KEY (`customer_station_track_id`) REFERENCES `customer_station_tracks` (`id`), 
    CONSTRAINT `trm_customer` FOREIGN KEY (`customer_id`) REFERENCES `customers` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION, 
    CONSTRAINT `trm_station` FOREIGN KEY (`station_id`) REFERENCES `stations` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION, 
    CONSTRAINT `trm_train` FOREIGN KEY (`train_id`) REFERENCES `trains` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION, 
    CONSTRAINT `trm_trainrun` FOREIGN KEY (`train_run_id`) REFERENCES `train_runs` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION 
) ENGINE=InnoDB AUTO_INCREMENT=9928724 DEFAULT CHARSET=utf8; 

У нас есть много запросов, фильтр Customer_ID и метки времени, поэтому мы создали комбинированный индекс для этого.

Теперь у меня есть этот простой запрос:

SELECT * FROM `train_run_messages` WHERE `customer_id` = '5' AND `timestamp` >= '2013-12-01 00:00:57' AND `timestamp` <= '2013-12-31 23:59:59' LIMIT 0, 100 

На нашей текущей машине с ~ записей 10Х этот запрос занимает ~ 16 секунд, что является способом долго в моем вкусе, так как есть индекс для запросов, таких как это.

Так давайте посмотрим на выходе объяснить этого запроса:

+----+-------------+--------------------+------+------------------------------------------- +--------------------+---------+-------+--------+-------------+ 
| id | select_type | table    | type | possible_keys        | key    | key_len | ref | rows  | Extra  | 
+----+-------------+--------------------+------+-------------------------------------------+--------------------+---------+-------+--------+-------------+ 
| 1 | SIMPLE  | train_run_messages | ref | timestamp,customer_timestmap,trm_customer | customer_timestamp | 4  | const | 551405  | Using where | 
+----+-------------+--------------------+------+-------------------------------------------+--------------------+---------+-------+--------+-------------+ 

Так MySQL говорит мне, что он будет использовать индекс customer_timestamp, отлично! Почему запрос все еще занимает ~ 16 секунд? Поскольку я не всегда доверяю анализатор запросов MySQL позволяет попробовать его с натянутой индекс:

SELECT * FROM `train_run_messages` USE INDEX (customer_timestamp) WHERE `customer_id` = '5' AND `timestamp` >= '2013-12-01 00:00:57' AND `timestamp` <= '2013-12-31 23:59:59' LIMIT 0, 100 

запросов Время: 0.079s !!

Me: озадаченный!

Так может ли кто-нибудь объяснить, почему MySQL явно не использует индекс, который, по его словам, будет использоваться с выходом EXPLAIN? И есть ли способ доказать, какой индекс он действительно использовал при выполнении реального запроса?

Btw: Вот выход из медленного журнала:

# Time: 131217 11:18:04 
# [email protected]: root[root] @ localhost [127.0.0.1] 
# Query_time: 16.252878 Lock_time: 0.000168 Rows_sent: 100 Rows_examined: 9830711 
SET timestamp=1387275484; 
SELECT * FROM `train_run_messages` WHERE `customer_id` = '5' AND `timestamp` >= '2013-12-01 00:00:57' AND `timestamp` <= '2013-12-31 23:59:59' LIMIT 0, 100; 

Alltough это конкретно не говорит, что он не использует какое-либо индексирование Rows_examined предполагает, что он делает полный просмотр таблицы.

Так это можно устранить без использования USE INDEX? Мы используем Propel в качестве ORM, и в настоящее время нет способа использовать «USE INDEX» для MySQL, без ручного ввода запроса.

Edit: Вот вывод EXPLAIN и USE INDEX:

+----+-------------+--------------------+-------+--------------------+--------------------+---------+------+--------+-------------+ 
| id | select_type | table    | type | possible_keys  | key    | key_len | ref | rows | Extra  | 
+----+-------------+--------------------+-------+--------------------+--------------------+---------+------+--------+-------------+ 
| 1 | SIMPLE  | train_run_messages | range | customer_timestmap | customer_timestmap | 8  | NULL | 191264 | Using where | 
+----+-------------+--------------------+-------+--------------------+--------------------+---------+------+--------+-------------+ 
+0

Сколько различных идентификаторов клиентов существует? – Kickstart

+0

В таблице train_run_messages есть только записи с customerId 5. (Система предназначена для нескольких клиентов, но в этой базе данных есть только один клиент) – Shyru

+0

В этом случае он будет игнорировать индекс на customer_id (эмпирическое правило, оно похоже, если индекс не сужает записи ниже примерно 1/3, то он будет проигнорирован). Однако я бы ожидал, что временные штампы будут сужать его больше, чем это – Kickstart

ответ

0

MySQL имеет три кандидатские индексы

  • (метка времени)
  • (customer_id, метка времени)
  • (customer_id)

и вы спрашиваете

`customer_id` = '5' AND `timestamp` BETWEEN ? AND ? 

оптимизатор выбрать из статистики.

Оптимизатор InnoDB Engine зависит от статистики, которая использует выборку при открытии таблицы. выборка по умолчанию считывает 8 страниц в индексном файле.

Итак, я предлагаю три вещи следующим образом

  1. увеличение innodb_stats_sample_pages=64.
  2. индекса удалить redandant. следующий индекс просто отлично. В настоящее время существует только customer_id = 5 (вы сказали)
    • (метка времени)
    • (customer_id)
  3. запустить OPTIMIZE TABLE train_run_messages реорганизовать таблицу.
    • это уменьшает таблицу и индекс размера и иногда это делает оптимизатор умнее
+0

Правильно ли я понимаю ваш ответ, что он не объясняет, почему в объяснении говорится, что он будет использовать (customer_id, timestamp), где на самом деле он не будет использовать его при выполнении запроса? Это связано со статистикой таблицы и innodb_stats_sample_pages? – Shyru

+0

@Shyru я так думаю. в отличие от статистики, основанной на гистограмме Postgresql, InnoDB зависит от выборки, а 'innodb_stats_sample_pages' - единственный способ точной статистики. Если статистика, основанная на гистограмме, Postgresql не будет использоваться (customer_id, timestamp), он уже знает, что только 5 customer_id. –

+0

Но почему EXPLAIN говорит, что он будет использовать индекс customer_timestamp, но на самом деле он не использует этот индекс? – Shyru

0

Для меня самая большая вещь, это не удается на вашем обертывание идентификатор клиента в кавычках ... такие как = «5 ». Делая это, он не может использовать индекс customer/timestamp, потому что идентификатор клиента должен быть преобразован в строку, чтобы соответствовать вашему «5» против just = 5, и вам должно быть хорошо идти.

+0

Не уверен, что это правильно при сравнении с числовым столбцом (он будет выполнять преобразование только один раз), хотя это было бы правдой, если бы использовалась некотируемая номер для сравнения со строковым столбцом. – Kickstart

+0

@Kickstart, точка ...если индексированный столбец является числовым и пытается по какой-либо причине преобразовать столбец данных в строку в соответствие с = 5, он также не сможет использовать индекс, а также фактический запрос с использованием столбца = 5 (с тем же ожидаемым тип данных), не выполняется преобразование или ложная интерпретация для решения плана выполнения. – DRapp

+0

Я считаю, что он может (и, конечно же, при быстром тестировании), поскольку ему нужно только один раз перевести «5» в 5 и затем использовать индекс. Это накладные расходы, но крошечные и не будут увеличиваться по мере увеличения количества строк. Проблема возникает при сравнении столбца без кавычек 5 с столбцом char, где многие различные значения символов могут быть оценены на число 5. В этом случае MySQL должен был бы преобразовать каждую строку в числовую, следовательно, не имея возможности использовать какой-либо индекс. – Kickstart

Смежные вопросы