Вот моя таблица с ~ 10000000 строк данных
CREATE TABLE `votes` (
`subject_name` varchar(32) COLLATE utf8_unicode_ci NOT NULL,
`subject_id` int(11) NOT NULL,
`voter_id` int(11) NOT NULL,
`rate` int(11) NOT NULL,
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`subject_name`,`subject_id`,`voter_id`),
KEY `IDX_518B7ACFEBB4B8AD` (`voter_id`),
KEY `subject_timestamp` (`subject_name`,`subject_id`,`updated_at`),
KEY `voter_timestamp` (`voter_id`,`updated_at`),
CONSTRAINT `FK_518B7ACFEBB4B8AD` FOREIGN KEY (`voter_id`) REFERENCES `users` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
Вот показатели кардинальности
Поэтому, когда я делаю этот запрос:
SELECT SQL_NO_CACHE * FROM votes WHERE
voter_id = 1099 AND
rate = 1 AND
subject_name = 'medium'
ORDER BY updated_at DESC
LIMIT 20 OFFSET 100;
Я ожидал, что он использует индекс, voter_timestamp
но MySQL предпочитает использовать это вместо:
explain select SQL_NO_CACHE * from votes where subject_name = 'medium' and voter_id = 1001 and rate = 1 order by updated_at desc limit 20 offset 100;`
type:
index_merge
possible_keys:
PRIMARY,IDX_518B7ACFEBB4B8AD,subject_timestamp,voter_timestamp
key:
IDX_518B7ACFEBB4B8AD,PRIMARY
key_len:
102,98
ref:
NULL
rows:
9255
filtered:
10.00
Extra:
Using intersect(IDX_518B7ACFEBB4B8AD,PRIMARY); Using where; Using filesort
И я получил время запроса 200-400 мс.
Если я заставлю это использовать правильный индекс как:
SELECT SQL_NO_CACHE * FROM votes USE INDEX (voter_timestamp) WHERE
voter_id = 1099 AND
rate = 1 AND
subject_name = 'medium'
ORDER BY updated_at DESC
LIMIT 20 OFFSET 100;
Mysql может вернуть результаты в 1-2 мс
и вот объяснение:
type:
ref
possible_keys:
voter_timestamp
key:
voter_timestamp
key_len:
4
ref:
const
rows:
18714
filtered:
1.00
Extra:
Using where
Так почему же MySQL не выбрал voter_timestamp
индекс для моего исходного запроса?
То , что я пытался это analyze table votes
, optimize table votes
, падение этого индекса и добавить его снова, но MySQL до сих пор использует неправильный индекс. не совсем понимаю в чем проблема.
subject_name = "medium"
часть, она также может выбрать правильный индекс, нет необходимости индексироватьrate
(voter_id, updated_at)
. Другой индекс будет(voter_id, subject_name, updated_at)
или(subject_name, voter_id, updated_at)
(без ставки).subject_name='medium' and rate=1
)LIMIT
или дажеORDER BY
если только индекс сначала не удовлетворит всю фильтрацию. То есть без полных четырех столбцов он соберет все соответствующие строки, отсортирует их все, а затем выберетLIMIT
. С индексом в 4-столбца, запрос может избежать сортировки и остановки после прочтения только теLIMIT
строки.Ответы:
MySQL использует относительно простую (более простую, чем другие СУБД) модель затрат для планирования запросов, в которых фильтрация вашего набора данных имеет достаточно высокий приоритет. В вашем первом запросе с индексом слияния предполагается, что сканирование ~ 9000 строк будет необходимо, в то время как второй запрос с подсказкой индекса потребует 18000. Моя ставка будет состоять в том, что это весит в вычислениях достаточно для перемещения шкалы к слиянию , Вы можете подтвердить это (или найти другие причины), включив
optimizer_trace
, выполнить свой запрос и оценить результаты.Одно замечание
index_merge
: в большинстве случаев вы обнаружите, что это довольно дорого. Хотя это очень полезно для сценариев типа OLAP, оно может не очень хорошо подходить для OLTP, поскольку операция может занять значительное время вашего запроса и, как вы можете видеть, иногда неоптимальный план выполнения на самом деле быстрее.К счастью, MySQL предоставляет переключатели для оптимизатора, поэтому вы можете настроить его по своему желанию.
Для всех опций вы можете запустить:
Для его замены вам не нужно копировать и вставлять всю строку. Это работает, как
dict.update()
в Python.Если возможно, я бы также посмотрел на структуру вашего стола и улучшил. Наличие первичного ключа длиной ~ 100 байт со многими дополнительными ключами не рекомендуется.
У вас есть четыре вторичных ключа, и некоторые из них излишни, например,
(voter_id)
индекс является подмножеством(voter_id, updated_at)
источник
OR
вUNION
часто , как хорошо или лучше.Для этого запроса вам нужен этот индекс:
updated_at
Должен быть последним; остальные три могут быть в любом порядке. (3-колоночные индексы ypercube не очень полезны, так как они не заканчиваютWHERE
столбцы перед попаданием вORDER BY
столбец.)Добавив этот индекс, вы, вероятно, сможете избавиться от всех остальных вторичных ключей:
KEY
IDX_518B7ACFEBB4B8AD
(voter_id
), - The FK можно использовать указательный KEYsubject_timestamp
(subject_name
,subject_id
,updated_at
), - в основном избыточный KEYvoter_timestamp
(voter_id
,updated_at
), - возможно, были ваши попыткиС индексом из 4 столбцов у вас есть шанс оптимизировать «нумерацию страниц» и избежать
OFFSET
. Смотрите этот блог.В другой теме ... Когда я вижу ,
X_name
иX_id
, я полагаю , «нормализация» происходит. Я ожидаю увидеть эти два столбца в таблице, практически ничего другого. Я не ожидал бы увидеть оба в другой таблице.(voter_id, updated_at)
не пройдет,voter_id
так как он не закончил с фильтрацией (WHERE
). Затем, поскольку другой индекс меньше, он выбирается. У меня есть 3 столбца для фильтрации, затем столбец дляORDER BY
.источник