MySQL не подбирает оптимальный индекс - PullRequest
0 голосов
/ 24 мая 2018

Вот моя таблица:

CREATE TABLE `idx_weight` (
  `ID` bigint(20) NOT NULL AUTO_INCREMENT,
  `SECURITY_ID` bigint(20) NOT NULL COMMENT,
  `CONS_ID` bigint(20) NOT NULL,
  `EFF_DATE` date NOT NULL,
  `WEIGHT` decimal(9,6) DEFAULT NULL,
  PRIMARY KEY (`ID`),
  UNIQUE KEY `BPK_AK` (`SECURITY_ID`,`CONS_ID`,`EFF_DATE`),
  KEY `idx_weight_ix` (`SECURITY_ID`,`EFF_DATE`)
) ENGINE=InnoDB AUTO_INCREMENT=75334536 DEFAULT CHARSET=utf8

Для запроса 1:

explain select SECURITY_ID, min(EFF_DATE) as startDate, max(EFF_DATE) as endDate from idx_weight where security_id = 1782:

+----+-------------+------------+------+----------------------+---------------+---------+-------+--------+-------------+
| id | select_type | table      | type | possible_keys        | key           | key_len | ref   | rows   | Extra       |
+----+-------------+------------+------+----------------------+---------------+---------+-------+--------+-------------+
|  1 | SIMPLE      | idx_weight | ref  | BPK_AK,idx_weight_ix | idx_weight_ix | 8       | const | 887856 | Using index |
+----+-------------+------------+------+----------------------+---------------+---------+-------+--------+-------------+

Этот запрос выполняется нормально.

Теперь запрос 2 (единственное, что изменилось, это параметр security_id):

explain select SECURITY_ID, min(EFF_DATE) as startDate, max(EFF_DATE) as endDate from idx_weight where security_id = 26622:

+----+-------------+------------+------+----------------------+--------+---------+-------+----------+-------------+
| id | select_type | table      | type | possible_keys        | key    | key_len | ref   | rows     | Extra       |
+----+-------------+------------+------+----------------------+--------+---------+-------+----------+-------------+
|  1 | SIMPLE      | idx_weight | ref  | BPK_AK,idx_weight_ix | BPK_AK | 8       | const | 10700002 | Using index |
+----+-------------+------------+------+----------------------+--------+---------+-------+----------+-------------+

Обратите внимание, что он получает индекс BPK_AK и фактическийзапрос выполняется более 1 минуты.

Это неверно.Второй раз занял более 10 секунд.Я предполагаю, что в первый раз индекс не находится в пуле буферов.

Обойти это можно, добавив group by security_id:

explain select SECURITY_ID, min(EFF_DATE) as startDate, max(EFF_DATE) as endDate from idx_weight where security_id = 26622 group by security_id:

+----+-------------+------------+-------+----------------------+---------------+---------+------+-------+---------------------------------------+
| id | select_type | table      | type  | possible_keys        | key           | key_len | ref  | rows  | Extra                                 |
+----+-------------+------------+-------+----------------------+---------------+---------+------+-------+---------------------------------------+
|  1 | SIMPLE      | idx_weight | range | BPK_AK,idx_weight_ix | idx_weight_ix | 8       | NULL | 10314 | Using where; Using index for group-by |
+----+-------------+------------+-------+----------------------+---------------+---------+------+-------+---------------------------------------+

Но я до сих пор не понимаю, почему mysql не выбирает idx_weight_ix для некоторых security_id, что является индексом покрытия для этого запроса (и намного дешевле).Есть идеи?

==============================================================================

Обновление: @oysteing Узнал новый трюк, круто!:)

Вот трассировка оптимизатора:

Запрос 1: https://gist.github.com/aping/c4388d49d666c43172a856d77001f4ce

Запрос 2: https://gist.github.com/aping/1af5504b428ca136a8b1c41c40d763e4

И некоторая дополнительная информация, которая может бытьполезно:

От INFORMATION_SCHEMA.STATISTICS:

+------------+---------------+--------------+-------------+-------------+
| NON_UNIQUE | INDEX_NAME    | SEQ_IN_INDEX | COLUMN_NAME | CARDINALITY |
+------------+---------------+--------------+-------------+-------------+
|          0 | BPK_AK        |            1 | SECURITY_ID |       74134 |
|          0 | BPK_AK        |            2 | CONS_ID     |      638381 |
|          0 | BPK_AK        |            3 | EFF_DATE    |    68945218 |
|          1 | idx_weight_ix |            1 | SECURITY_ID |       61393 |
|          1 | idx_weight_ix |            2 | EFF_DATE    |      238564 |
+------------+---------------+--------------+-------------+-------------+

CARDINALITY для SECURITY_ID различны, но технически они должны быть абсолютно одинаковыми, я прав?

Из этого: https://dba.stackexchange.com/questions/49656/find-the-size-of-each-index-in-a-mysql-table?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa

+---------------+-------------------+
| index_name    | indexentry_length |
+---------------+-------------------+
| BPK_AK        |        1376940279 |
| idx_weight_ix |         797175951 |
+---------------+-------------------+

Размер индекса составляет около 800 МБ против 1,3 ГБ.

Работает select count(*) from idx_weight where security_id = 1782 возвращает 509994

и select count(*) from idx_weight where security_id = 26622возвращает 5828054

Затем принудительное использование BPK_AK для запроса 1:

select SQL_NO_CACHE SECURITY_ID, min(EFF_DATE) as startDate, max(EFF_DATE) as endDate from idx_weight use index (BPK_AK) where security_id = 1782 заняло 0,2 с.

Таким образом, в общем случае 26622 имеет в 10 раз больше строкчем 1782, но с использованием того же индекса это заняло в 50 раз больше времени.

PS: размер пула буферов составляет 25 ГБ.

Ответы [ 3 ]

0 голосов
/ 24 мая 2018

Я могу найти обходной путь, добавив группу по security_id

Ну, да.Я бы не стал делать это по-другому, поскольку, когда вы используете агрегатные функции, вам нужно группировать что-то.Я даже не знал, что MySQL позволил вам обойти это.

Я думаю, что @slaakso прав.Подними его.

0 голосов
/ 01 июня 2018

Оптимизатор трассировок показывает, что причина разницы в выборе индекса, связана с оценками, полученными от InnoDB.Для каждого потенциального индекса оптимизатор запрашивает у механизма хранения оценку количества записей в диапазоне.Для первого запроса он получает следующие оценки:

BPK_AK:       1031808
idx_weight_ix: 887856

Таким образом, оценочная стоимость чтения является самой низкой для idx_weight_ix, и этот индекс выбран.Для второго запроса следующие оценки:

BPK_AK:        11092112
idx_weight_ix: 12003098

А оценочная стоимость чтения BPK_AK самая низкая из-за меньшего количества строк.Можно сказать, что MySQL должен знать, что реальное число строк в диапазоне одинаково в обоих случаях, но эта логика не была реализована.

Я не знаю деталей того, как InnoDB вычисляет эти оценки,но в основном он выполняет два «индексных погружения», чтобы найти первую и последнюю строку в диапазоне, а затем каким-то образом вычисляет «расстояние» между ними.Возможно, на оценки влияют неиспользуемые места на страницах индекса, и что OPTIMIZE TABLE может это исправить, но выполнение OPTIMIZE TABLE, вероятно, займет очень много времени на такой большой таблице.

Самый быстрый способ решить эту проблему, это добавить предложение GROUP BY, как упомянуто здесь несколькими другими людьми.Тогда MySQL нужно будет только прочитать 2 строки на группу;первый и последний, так как индекс упорядочен EFF_DATE для каждого значения security_id.В качестве альтернативы вы можете использовать FORCE INDEX для принудительного определения определенного индекса.

Может также случиться, что MySQL 8.0 лучше обработает этот запрос.Модель стоимости несколько изменилась, и это повысит стоимость «холодных» индексов, которые не кэшируются в пуле буферов.

0 голосов
/ 24 мая 2018

Когда вы смешиваете обычные столбцы (SECURITY_ID) и агрегатные функции (min и max в вашем случае), вы должны использовать GROUP BY.Если вы этого не сделаете, MySQL является бесплатным, дайте любой результат, который пожелает.С GROUP BY вы получите правильный результат.В более новых базах данных MySQL такое поведение применяется по умолчанию.

Причина, по которой второй индекс не выбран при исключении GROUP BY, наиболее вероятна из-за того, что агрегатные функции не ограничены одной и той же группой (= security_id) abd, следовательно, не может использоваться в качестве ограничителя.

...