MySQL - Как я могу ускорить этот запрос
-
16-10-2019 - |
Вопрос
У меня есть следующие таблицы:
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`first_name` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`last_name` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`account_data` text COLLATE utf8_unicode_ci,
`created_at` datetime DEFAULT NULL,
`updated_at` datetime DEFAULT NULL,
`twitter_username` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`email` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`crypted_password` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`password_salt` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`persistence_token` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`single_access_token` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`perishable_token` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`login_count` int(11) NOT NULL DEFAULT '0',
`failed_login_count` int(11) NOT NULL DEFAULT '0',
`last_request_at` datetime DEFAULT NULL,
`current_login_at` datetime DEFAULT NULL,
`last_login_at` datetime DEFAULT NULL,
`current_login_ip` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`last_login_ip` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`is_admin` tinyint(1) DEFAULT '0',
`referrer_id` int(11) DEFAULT NULL,
`partner` tinyint(1) DEFAULT '0',
`subscription_type` varchar(255) COLLATE utf8_unicode_ci DEFAULT 'free',
`workflow_state` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`persona_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `persona_index` (`persona_id`)
) ENGINE=InnoDB
и таблица:
CREATE TABLE `user_actions` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) DEFAULT NULL,
`action_type` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`module` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`data` text COLLATE utf8_unicode_ci,
`timestamp` datetime DEFAULT NULL,
`created_at` datetime DEFAULT NULL,
`updated_at` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `user_id_index` (`user_id`),
KEY `action_type_index` (`action_type`),
KEY `user_action_type_index` (`user_id`,`action_type`),
KEY `timestamp_index` (`timestamp`),
KEY `user_id_timestamp_index` (`user_id`,`timestamp`)
) ENGINE=InnoDB
Проблема в следующем запросе:
SELECT user_actions.*, users.twitter_username, users.email FROM `user_actions`
INNER JOIN users ON (user_actions.user_id=users.id) ORDER BY timestamp DESC LIMIT 0, 30
Вот объяснение:
user_actions
The table was retrieved with this index: user_id_timestamp_index
You can speed up this query by querying only fields that are within the index. Or you can create an index that includes every field in your query, including the primary key.
Approximately 76 rows of this table were scanned.
users
This table was retrieved with a full table scan, which is often quite bad for performance, unless you only retrieve a few rows.
The table was retrieved with this index:
No index was used in this part of the query.
A temporary table was created to access this part of the query, which can cause poor performance. This typically happens if the query contains GROUP BY and ORDER BY clauses that list columns differently.
MySQL had to do an extra pass to retrieve the rows in sorted order, which is a cause of poor performance but sometimes unavoidable.
You can speed up this query by querying only fields that are within the index. Or you can create an index that includes every field in your query, including the primary key.
Approximately 3445 rows of this table were scanned.
Этот запрос занимает много времени, чтобы выполнить, какие идеи, как улучшить?
Решение
Вот ваш оригинальный запрос:
SELECT
user_actions.*,
users.twitter_username,
users.email
FROM
`user_actions`
INNER JOIN users
ON (user_actions.user_id=users.id)
ORDER BY timestamp
DESC LIMIT 0, 30
;
Во -первых, я замечаю, что вы присоединяетесь к двум целым таблицам. Поскольку тебе нужно только twitter_username
а также email
от users
таблица, вы должны присоединиться только к users
Используя три столбца: id
, twitter_username
а также email
.
Во -вторых, это LIMIT
пункт. Это выполняется после присоединения. Вы должны выполнить его перед присоединением. В вашем случае вы запрашиваете 30 самых последних действий пользователя. Если вы можете гарантировать, что только 30 строк останавливаются от user_actions
, соединение должно работать намного быстрее.
если ты Прочитайте ответ от @dtest, его первые две точки смены уже говорят вам, что не так, запрос из -за действий MySQL приведет к сбору данных из каждой таблицы. Ключ в том, чтобы понять, как будут выглядеть временные таблицы, когда запрос обрабатывается и где будут проживать данные (память или диск).
Что вам нужно сделать, так это рефактор запроса, чтобы обмануть оптимизатор MySQL запроса. Заставьте запрос произвести меньшие временные столы. В большинстве случаев изменения конфигурации в моем.cnf должны иметь драмамтическую разницу. В других случаях, таких как этот, рефакторирование запроса может быть достаточным.
Вот мое предложенное изменение вашего запроса, которое должно работать быстрее:
SELECT
ua.*,
u.twitter_username,
u.email
FROM
(SELECT * FROM `user_actions`
ORDER BY timestamp DESC LIMIT 30) ua
LEFT JOIN
(SELECT id,twitter_username,email FROM `users`) u
ON (ua.user_id=u.id)
;
Вот причины для рефакторирования запроса:
Причина № 1
Если вы посмотрите на встроенный стол ua
, Я получаю только 30 строк, используя LIMIT
. Это произойдет независимо от того, насколько велик user_actions
Таблица получает. Анкет Это уже упорядочено, потому что ORDER BY timestamp DESC
происходит до LIMIT
.
Причина № 2
Если вы посмотрите встроенный таблицу u
, оно имеет id
,twitter_username
,email
. Анкет А id
необходим для реализации соединения.
Причина № 3
я использую LEFT JOIN
вместо INNER JOIN
По двум (2) причинам:
- Сохранить порядок запроса на основе
ua
- Отображать все действия пользователя в случае, если user_id в
ua
больше не существует вusers
столы.
Делать эти вещи заставит временные таблицы быть меньше. Тем не менее, вам все равно понадобится Реализуйте BulletPoint #3 из ответа @dtest Чтобы превзойти наличие температурных столов, приземленных на диск.
Другие советы
Ну, главная проблема заключается в том, что, поскольку на вашем запросе нет фильтрации (нет WHERE
утверждение), он помещает все ряды столбцами user_actions.*, twitter_username, email
во временную таблицу, чтобы сделать сортировку.
Поэтому первое, что я сделал бы, это попытаться ограничить количество строк, которые входят в ваш набор результатов. Например, я бы сказал, добавив WHERE timestamp > DATE_SUB(NOW(), INTERVAL 7 DAY)
Чтобы получить результаты только в течение последних 7 дней (если это приемлемо для вашего варианта использования).
Далее я бы изменил запрос, чтобы вытащить необходимые столбцы из user_actions
Чтобы уменьшить объем информации, необходимой для помещения во временную таблицу.
Теперь, когда вы можете или не могли быть удалены строки/столбцы, которые необходимо размещать во временной таблице, чтобы отсортировать, давайте посмотрим, как MySQL обрабатывает временные таблицы. Из документации по tmp_table_size
переменная (акцент добавлен):
Максимальный размер внутренних временных таблиц в памяти. (Фактический предел определяется как минимум TMP_TABLE_SIZE и MAX_HEAP_TABLE_SIZE.)1 Если временная таблица в памяти превышает предел, MySQL автоматически преобразует его в таблицу MyISAM на диске.
Во -первых, позвольте мне указать о предостережении, представленном SuperScript 1: Размер временной таблицы, созданной в памяти, является минимумом любого из них tmp_table_size
или же max_heap_table_size
, поэтому, если вы увеличиваете один, обязательно увеличите другое.
Если объем ваших данных превышает размер минимума этих двух переменных, он будет размещен на диске. Диск медленный. Не делайте диск, если вы можете избежать этого!
Чтобы подтвердить:
Ограничьте количество рядов, которые вы сортируете, используя
WHERE
. Анкет Даже если вы делаетеLIMIT
, все ряды все еще помещаются во временный стол для сортировки.Ограничьте количество столбцов, которые вы запрашиваете. Если они вам не нужны, не просите их.
Последняя среда, увеличьте размер
tmp_table_size
а такжеmax_heap_table_size
Если запрос увеличивает вашCreated_tmp_disk_tables
переменная статуса. Кроме того, не увеличивайте это резко. Это может иметь влияние на производительность, в зависимости от вашего оборудования и количества оперативной памяти, который у вас есть на вашем сервере.