Почему оптимизатор MySQL не использует индекс всех столбцов?
Percona MySQL 5.7
схема таблицы:
CREATE TABLE Developer.Rate (
ID bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
TIME datetime NOT NULL,
BASE varchar(3) NOT NULL,
QUOTE varchar(3) NOT NULL,
BID double NOT NULL,
ASK double NOT NULL,
PRIMARY KEY (ID),
INDEX IDX_TIME (TIME),
UNIQUE INDEX IDX_UK (BASE, QUOTE, TIME)
)
ENGINE = INNODB
ROW_FORMAT = COMPRESSED;
Я пытаюсь сделать запрос на последние данные до выбранного периода. Оптимизатор использует не полный уникальный ключ, только 2 столбца из 3.
Если я делаю запрос в обычном порядке:
EXPLAIN FORMAT=JSON
SELECT
BID
FROM
Rate
WHERE
BASE = 'EUR'
AND QUOTE = 'USD'
AND `TIME` <= (NOW() - INTERVAL 1 MONTH)
ORDER BY
`TIME` DESC
LIMIT 1
;
"Объяснить" показывает, что используются только 2 первых столбца индекса: BASE, QUOTE
{
"query_block": {
"select_id": 1,
"cost_info": {
"query_cost": "10231052.40"
},
"ordering_operation": {
"using_filesort": false,
"table": {
"table_name": "Rate",
"access_type": "ref",
"possible_keys": [
"IDX_UK",
"IDX_TIME"
],
"key": "IDX_UK",
"used_key_parts": [
"BASE",
"QUOTE"
],
"key_length": "22",
"ref": [
"const",
"const"
],
"rows_examined_per_scan": 45966462,
"rows_produced_per_join": 22983231,
"filtered": "50.00",
"cost_info": {
"read_cost": "1037760.00",
"eval_cost": "4596646.20",
"prefix_cost": "10231052.40",
"data_read_per_join": "1G"
},
"used_columns": [
"ID",
"TIME",
"BASE",
"QUOTE",
"BID"
],
"attached_condition": "((`Developer`.`Rate`.`BASE` <=> 'EUR') and (`Developer`.`Rate`.`QUOTE` <=> 'USD') and (`Developer`.`Rate`.`TIME` <= <cache>((now() - interval 1 month))))"
}
}
}
}
Но если вы заставите оптимизатор использовать IDX_UK, MySQL использует все 3 столбца в запросе:
EXPLAIN FORMAT=JSON
SELECT
BID
FROM
Rate FORCE INDEX(IDX_UK)
WHERE
BASE = 'EUR'
AND QUOTE = 'USD'
AND `TIME` <= (NOW() - INTERVAL 1 MONTH)
ORDER BY
`TIME` DESC
LIMIT 1
{
"query_block": {
"select_id": 1,
"cost_info": {
"query_cost": "10231052.40"
},
"ordering_operation": {
"using_filesort": false,
"table": {
"table_name": "Rate",
"access_type": "range",
"possible_keys": [
"IDX_UK"
],
"key": "IDX_UK",
"used_key_parts": [
"BASE",
"QUOTE",
"TIME"
],
"key_length": "27",
"rows_examined_per_scan": 45966462,
"rows_produced_per_join": 15320621,
"filtered": "100.00",
"index_condition": "((`Developer`.`Rate`.`BASE` = 'EUR') and (`Developer`.`Rate`.`QUOTE` = 'USD') and (`Developer`.`Rate`.`TIME` <= <cache>((now() - interval 1 month))))",
"cost_info": {
"read_cost": "1037760.00",
"eval_cost": "3064124.31",
"prefix_cost": "10231052.40",
"data_read_per_join": "818M"
},
"used_columns": [
"ID",
"TIME",
"BASE",
"QUOTE",
"BID"
]
}
}
}
}
Почему оптимизатор не использует все 3 столбца без явного объявления индекса?
Добавлено:
Я правильно понимаю, я должен использовать запрос, как это?
Пример:
EXPLAIN FORMAT=JSON
SELECT
BID
FROM
Rate
WHERE
BASE = 'EUR'
AND QUOTE = 'USD'
AND `TIME` <= (NOW() - INTERVAL 1 MONTH)
ORDER BY
BASE DESC, QUOTE DESC, TIME DESC
LIMIT 1
Если я правильно понимаю, вывод Explain не будет лучше. Там пока только 2 колонки используются без ВРЕМЕНИ
Объяснить вывод
{
"query_block": {
"select_id": 1,
"cost_info": {
"query_cost": "10384642.20"
},
"ordering_operation": {
"using_filesort": false,
"table": {
"table_name": "Rate",
"access_type": "ref",
"possible_keys": [
"IDX_UK",
"IDX_TIME"
],
"key": "IDX_UK",
"used_key_parts": [
"BASE",
"QUOTE"
],
"key_length": "22",
"ref": [
"const",
"const"
],
"rows_examined_per_scan": 46734411,
"rows_produced_per_join": 23367205,
"filtered": "50.00",
"index_condition": "((
Developer
, Rate
, BASE
<=> 'EUR') и (Developer
, Rate
, QUOTE
<=> 'USD') и (Developer
, Rate
, TIME
<= ((now() - интервал 1 месяц))))",
"cost_info": {
"read_cost": "1037760.00",
"eval_cost": "4673441.10",
"prefix_cost": "10384642.20",
"data_read_per_join": "1G"
},
"used_columns": [
"ID",
"TIME",
"BASE",
"QUOTE",
"BID"
]
}
}
}
}
Добавлено 2:
Я сделал эти 4 запроса:
- 1 -
<code>FLUSH STATUS;
SELECT
BID
FROM
Rate
WHERE
BASE = 'EUR'
AND QUOTE = 'USD'
AND `TIME` <= (NOW() - INTERVAL 1 MONTH)
LIMIT 1;
SHOW SESSION STATUS LIKE 'Handler%';</code>
- 2 -
<code>FLUSH STATUS;
SELECT
BID
FROM
Rate FORCE INDEX (IDX_UK)
WHERE
BASE = 'EUR'
AND QUOTE = 'USD'
AND `TIME` <= (NOW() - INTERVAL 1 MONTH)
LIMIT 1;
SHOW SESSION STATUS LIKE 'Handler%';
</code>
- 3 -
<code>FLUSH STATUS;
SELECT
BID
FROM
Rate
WHERE
BASE = 'EUR'
AND QUOTE = 'USD'
AND `TIME` <= (NOW() - INTERVAL 1 MONTH)
ORDER BY
`TIME` DESC
LIMIT 1;
SHOW SESSION STATUS LIKE 'Handler%';</code>
- 4 -
<code>
FLUSH STATUS;
SELECT
BID
FROM
Rate FORCE INDEX (IDX_UK)
WHERE
BASE = 'EUR'
AND QUOTE = 'USD'
AND `TIME` <= (NOW() - INTERVAL 1 MONTH)
ORDER BY
`TIME` DESC
LIMIT 1;
SHOW SESSION STATUS LIKE 'Handler%';</code>
Выходные данные session_status одинаковы во всех запросах, кроме запроса 3. В выходных данных запроса 3: Handler_read_prev = 486474; В выводе всех других запросов: Handler_read_prev = 0;
Добавлено 3:
Я сделал копию таблицы, удалил поле Id, выдвинул UNIQUE ключ как ПЕРВИЧНЫЙ.
Схема:
CREATE TABLE Developer.Rate2 (
TIME datetime NOT NULL,
BASE varchar(3) NOT NULL,
QUOTE varchar(3) NOT NULL,
BID double NOT NULL,
ASK double NOT NULL,
PRIMARY KEY (BASE, QUOTE, TIME),
INDEX IDX_BID_ASK (BID, ASK)
)
ENGINE = INNODB
AVG_ROW_LENGTH = 26
CHARACTER SET utf8
COLLATE utf8_general_ci
ROW_FORMAT = COMPRESSED;
{
"query_block": {
"select_id": 1,
"cost_info": {
"query_cost": "9673452.20"
},
"ordering_operation": {
"using_filesort": false,
"table": {
"table_name": "Rate2",
"access_type": "range",
"possible_keys": [
"PRIMARY"
],
"key": "PRIMARY",
"used_key_parts": [
"BASE",
"QUOTE",
"TIME"
],
"key_length": "27",
"rows_examined_per_scan": 48023345,
"rows_produced_per_join": 16006180,
"filtered": "100.00",
"cost_info": {
"read_cost": "68783.20",
"eval_cost": "3201236.12",
"prefix_cost": "9673452.20",
"data_read_per_join": "732M"
},
"used_columns": [
"TIME",
"BASE",
"QUOTE",
"BID"
],
"attached_condition": "((`Developer`.`Rate2`.`BASE` = 'EUR') and (`Developer`.`Rate2`.`QUOTE` = 'USD') and (`Developer`.`Rate2`.`TIME` <= <cache>((now() - interval 1 month))))"
}
}
}
}
Теперь запрос действительно работает, и Explain показывает, что используются все 3 столбца. Этот вариант работает.
2 ответа
Избавляться от ID
это бесполезно. Продвиньте свой UNIQUE
ключ к PRIMARY
, Теперь, по волшебству, запрос будет быстрее, и заданный вами вопрос станет спорным. (Вам также может понадобиться DESC
уловка, которую предложила Лорейн.)
Вот еще один метод для сравнения производительности:
FLUSH STATUS;
SELECT ...;
SHOW SESSION STATUS LIKE 'Handler%';
Мне было бы интересно увидеть выход из SHOW
для с и без DESC
трюк. И с / без FORCE INDEX
Вы намекали на.
Почему быстрее? Ваш запрос использовал вторичный индекс, но ему нужно bid
, который не был "покрыт" индексом. Получить bid
, PRIMARY KEY
нужно было детализировать в "данных". Изменяя его таким образом, чтобы использовать PK, эта дополнительная развертка исключается.
Поведение, которое вы описываете (ссылка на доступ вместо доступа к диапазону через большее количество столбцов) напоминает мне об ошибке #81341 и ошибке # 87613. Эти ошибки были исправлены в MySQL 5.7.17 и 5.7.21, соответственно. Какую версию вы используете?