Как оптимизировать этот запрос MySQL? Миллионы строк
У меня есть следующий запрос:
SELECT
analytics.source AS referrer,
COUNT(analytics.id) AS frequency,
SUM(IF(transactions.status = 'COMPLETED', 1, 0)) AS sales
FROM analytics
LEFT JOIN transactions ON analytics.id = transactions.analytics
WHERE analytics.user_id = 52094
GROUP BY analytics.source
ORDER BY frequency DESC
LIMIT 10
Таблица аналитики имеет 60M строк, а таблица транзакций имеет 3M строки.
Когда я запускаю EXPLAIN
по этому запросу, я получаю:
+------+--------------+-----------------+--------+---------------------+-------------------+----------------------+---------------------------+----------+-----------+-------------------------------------------------+
| # id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | |
+------+--------------+-----------------+--------+---------------------+-------------------+----------------------+---------------------------+----------+-----------+-------------------------------------------------+
| '1' | 'SIMPLE' | 'analytics' | 'ref' | 'analytics_user_id | analytics_source' | 'analytics_user_id' | '5' | 'const' | '337662' | 'Using where; Using temporary; Using filesort' |
| '1' | 'SIMPLE' | 'transactions' | 'ref' | 'tran_analytics' | 'tran_analytics' | '5' | 'dijishop2.analytics.id' | '1' | NULL | |
+------+--------------+-----------------+--------+---------------------+-------------------+----------------------+---------------------------+----------+-----------+-------------------------------------------------+
Я не могу понять, как оптимизировать этот запрос, поскольку он уже очень простой. Для выполнения этого запроса требуется около 70 секунд.
Вот те индексы, которые существуют:
+-------------+-------------+----------------------------+---------------+------------------+------------+--------------+-----------+---------+--------+-------------+----------+----------------+
| # Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-------------+-------------+----------------------------+---------------+------------------+------------+--------------+-----------+---------+--------+-------------+----------+----------------+
| 'analytics' | '0' | 'PRIMARY' | '1' | 'id' | 'A' | '56934235' | NULL | NULL | '' | 'BTREE' | '' | '' |
| 'analytics' | '1' | 'analytics_user_id' | '1' | 'user_id' | 'A' | '130583' | NULL | NULL | 'YES' | 'BTREE' | '' | '' |
| 'analytics' | '1' | 'analytics_product_id' | '1' | 'product_id' | 'A' | '490812' | NULL | NULL | 'YES' | 'BTREE' | '' | '' |
| 'analytics' | '1' | 'analytics_affil_user_id' | '1' | 'affil_user_id' | 'A' | '55222' | NULL | NULL | 'YES' | 'BTREE' | '' | '' |
| 'analytics' | '1' | 'analytics_source' | '1' | 'source' | 'A' | '24604' | NULL | NULL | 'YES' | 'BTREE' | '' | '' |
| 'analytics' | '1' | 'analytics_country_name' | '1' | 'country_name' | 'A' | '39510' | NULL | NULL | 'YES' | 'BTREE' | '' | '' |
| 'analytics' | '1' | 'analytics_gordon' | '1' | 'id' | 'A' | '56934235' | NULL | NULL | '' | 'BTREE' | '' | '' |
| 'analytics' | '1' | 'analytics_gordon' | '2' | 'user_id' | 'A' | '56934235' | NULL | NULL | 'YES' | 'BTREE' | '' | '' |
| 'analytics' | '1' | 'analytics_gordon' | '3' | 'source' | 'A' | '56934235' | NULL | NULL | 'YES' | 'BTREE' | '' | '' |
+-------------+-------------+----------------------------+---------------+------------------+------------+--------------+-----------+---------+--------+-------------+----------+----------------+
+----------------+-------------+-------------------+---------------+-------------------+------------+--------------+-----------+---------+--------+-------------+----------+----------------+
| # Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+----------------+-------------+-------------------+---------------+-------------------+------------+--------------+-----------+---------+--------+-------------+----------+----------------+
| 'transactions' | '0' | 'PRIMARY' | '1' | 'id' | 'A' | '2436151' | NULL | NULL | '' | 'BTREE' | '' | '' |
| 'transactions' | '1' | 'tran_user_id' | '1' | 'user_id' | 'A' | '56654' | NULL | NULL | '' | 'BTREE' | '' | '' |
| 'transactions' | '1' | 'transaction_id' | '1' | 'transaction_id' | 'A' | '2436151' | '191' | NULL | 'YES' | 'BTREE' | '' | '' |
| 'transactions' | '1' | 'tran_analytics' | '1' | 'analytics' | 'A' | '2436151' | NULL | NULL | 'YES' | 'BTREE' | '' | '' |
| 'transactions' | '1' | 'tran_status' | '1' | 'status' | 'A' | '22' | NULL | NULL | 'YES' | 'BTREE' | '' | '' |
| 'transactions' | '1' | 'gordon_trans' | '1' | 'status' | 'A' | '22' | NULL | NULL | 'YES' | 'BTREE' | '' | '' |
| 'transactions' | '1' | 'gordon_trans' | '2' | 'analytics' | 'A' | '2436151' | NULL | NULL | 'YES' | 'BTREE' | '' | '' |
+----------------+-------------+-------------------+---------------+-------------------+------------+--------------+-----------+---------+--------+-------------+----------+----------------+
Упрощенная схема для двух таблиц, прежде чем добавлять какие-либо дополнительные индексы, как было предложено, поскольку это не улучшило ситуацию.
CREATE TABLE 'analytics' (
'id' int(11) NOT NULL AUTO_INCREMENT,
'user_id' int(11) DEFAULT NULL,
'affil_user_id' int(11) DEFAULT NULL,
'product_id' int(11) DEFAULT NULL,
'medium' varchar(45) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
'source' varchar(45) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
'terms' varchar(1024) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
'is_browser' tinyint(1) DEFAULT NULL,
'is_mobile' tinyint(1) DEFAULT NULL,
'is_robot' tinyint(1) DEFAULT NULL,
'browser' varchar(45) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
'mobile' varchar(45) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
'robot' varchar(45) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
'platform' varchar(45) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
'referrer' varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
'domain' varchar(45) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
'ip' varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
'continent_code' varchar(10) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
'country_name' varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
'city' varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
'date' timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY ('id'),
KEY 'analytics_user_id' ('user_id'),
KEY 'analytics_product_id' ('product_id'),
KEY 'analytics_affil_user_id' ('affil_user_id')
) ENGINE=InnoDB AUTO_INCREMENT=64821325 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE 'transactions' (
'id' int(11) NOT NULL AUTO_INCREMENT,
'transaction_id' varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
'user_id' int(11) NOT NULL,
'pay_key' varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
'sender_email' varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
'amount' decimal(10,2) DEFAULT NULL,
'currency' varchar(10) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
'status' varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
'analytics' int(11) DEFAULT NULL,
'ip_address' varchar(46) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
'session_id' varchar(60) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
'date' timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
'eu_vat_applied' int(1) DEFAULT '0',
PRIMARY KEY ('id'),
KEY 'tran_user_id' ('user_id'),
KEY 'transaction_id' ('transaction_id'(191)),
KEY 'tran_analytics' ('analytics'),
KEY 'tran_status' ('status')
) ENGINE=InnoDB AUTO_INCREMENT=10019356 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
Если вышеизложенное не может быть оптимизировано дальше. Любая рекомендация по внедрению сводных таблиц будет отличной. Мы используем стек LAMP на AWS. Вышеуказанный запрос выполняется в RDS (m1.large).
Ответы
Ответ 1
Я бы создал следующие индексы (индексы b-tree):
analytics(user_id, source, id)
transactions(analytics, status)
Это отличается от предложения Гордона.
Важен порядок столбцов в индексе.
Вы фильтруете специальную analytics.user_id
, поэтому это поле должно быть первым в индексе. Затем вы группируете analytics.source
. Чтобы избежать сортировки по source
это должно быть следующее поле индекса. Вы также ссылаетесь на analytics.id
, поэтому лучше иметь это поле как часть индекса, поставить его последним. Может ли MySQL читать только индекс и не касаться таблицы? Я не знаю, но это довольно легко проверить.
Индекс transactions
должен начинаться с analytics
, поскольку он будет использоваться в JOIN
. Нам также нужен status
.
SELECT
analytics.source AS referrer,
COUNT(analytics.id) AS frequency,
SUM(IF(transactions.status = 'COMPLETED', 1, 0)) AS sales
FROM analytics
LEFT JOIN transactions ON analytics.id = transactions.analytics
WHERE analytics.user_id = 52094
GROUP BY analytics.source
ORDER BY frequency DESC
LIMIT 10
Ответ 2
Сначала некоторый анализ...
SELECT a.source AS referrer,
COUNT(*) AS frequency, -- See question below
SUM(t.status = 'COMPLETED') AS sales
FROM analytics AS a
LEFT JOIN transactions AS t ON a.id = t.analytics AS a
WHERE a.user_id = 52094
GROUP BY a.source
ORDER BY frequency DESC
LIMIT 10
Если сопоставление от a
до t
является "один ко многим", тогда вам нужно подумать о том, имеют ли COUNT
и SUM
правильные значения или завышенные значения. По мере того как запрос стоит, они "завышены". JOIN
происходит до агрегации, поэтому вы рассчитываете количество транзакций и сколько их было завершено. Я предполагаю, что это желательно.
Примечание. Обычным шаблоном является COUNT(*)
; говоря, что COUNT(x)
подразумевает проверку x
наличие NULL
. Я подозреваю, что проверка не нужна?
Этот индекс обрабатывает WHERE
и "покрывает":
analytics: INDEX(user_id, source, id) -- user_id first
transactions: INDEX(analytics, status) -- in this order
GROUP BY
может или не может потребовать "сортировку". ORDER BY
, отличающийся от GROUP BY
, определенно будет нужен. И весь сгруппированный набор строк нужно будет отсортировать; для LIMIT
нет ярлыка.
Как правило, сводные таблицы ориентированы на дату. То есть, PRIMARY KEY
включает в себя "дату" и некоторые другие измерения. Может быть, манипулирование по дате и user_id будет иметь смысл? Сколько транзакций в день имеет средний пользователь? Если не менее 10, то рассмотрим таблицу Сводной таблицы. Кроме того, важно не UPDATEing
или DELETEing
старые записи. Больше
Я, вероятно,
user_id ...,
source ...,
dy DATE ...,
status ...,
freq MEDIUMINT UNSIGNED NOT NULL,
status_ct MEDIUMINT UNSIGNED NOT NULL,
PRIMARY KEY(user_id, status, source, dy)
Тогда запрос становится
SELECT source AS referrer,
SUM(freq) AS frequency,
SUM(status_ct) AS completed_sales
FROM Summary
WHERE user_id = 52094
AND status = 'COMPLETED'
GROUP BY source
ORDER BY frequency DESC
LIMIT 10
Скорость исходит из многих факторов
- Меньшая таблица (меньше строк для просмотра)
- Нет
JOIN
- Более полезный индекс
(Ему все еще нужен дополнительный вид.)
Даже без сводной таблицы могут быть некоторые ускорения...
- Насколько велики таблицы? Насколько велика 'innodb_buffer_pool_size?
-
Normalizing
некоторых строк, которые являются объемными и повторяющимися, может сделать эту таблицу не связанной с I/O. - Это ужасно:
KEY (transaction_id(191))
; См. Здесь 5 способов исправить это. - IP-адресам не требуется 255 байт, а не
utf8mb4_unicode_ci
. (39) и ascii.
Ответ 3
Для этого запроса:
SELECT a.source AS referrer,
COUNT(*) AS frequency,
SUM( t.status = 'COMPLETED' ) AS sales
FROM analytics a LEFT JOIN
transactions t
ON a.id = t.analytics
WHERE a.user_id = 52094
GROUP BY a.source
ORDER BY frequency DESC
LIMIT 10 ;
Вам нужен индекс analytics(user_id, id, source)
и transactions(analytics, status)
.
Ответ 4
Попробуйте ниже и дайте мне знать, если это поможет.
SELECT
analytics.source AS referrer,
COUNT(analytics.id) AS frequency,
SUM(IF(transactions.status = 'COMPLETED', 1, 0)) AS sales
FROM (SELECT * FROM analytics where user_id = 52094) analytics
LEFT JOIN (SELECT analytics, status from transactions where analytics = 52094) transactions ON analytics.id = transactions.analytics
GROUP BY analytics.source
ORDER BY frequency DESC
LIMIT 10
Ответ 5
Не могли бы вы попробовать ниже Подход:
SELECT
analytics.source AS referrer,
COUNT(analytics.id) AS frequency,
SUM(sales) AS sales
FROM analytics
LEFT JOIN(
SELECT transactions.Analytics, (CASE WHEN transactions.status = 'COMPLETED' THEN 1 ELSE 0 END) AS sales
FROM analytics INNER JOIN transactions ON analytics.id = transactions.analytics
) Tra
ON analytics.id = Tra.analytics
WHERE analytics.user_id = 52094
GROUP BY analytics.source
ORDER BY frequency DESC
LIMIT 10
Ответ 6
Этот запрос потенциально объединяет миллионы analytics
записей с записями transactions
и вычисляет сумму (включая проверку состояния) на миллионах записей. Если мы сможем сначала применить LIMIT 10
а затем выполнить объединение и рассчитать сумму, мы можем ускорить запрос. К сожалению, нам нужна команда analytics.id
для соединения, которая теряется после применения GROUP BY
. Но, может быть, analytics.source
является достаточно избирательным, чтобы в любом случае увеличить запрос.
Поэтому моя идея состоит в том, чтобы вычислить частоты, ограниченные ими, для возврата analytics.source
и frequency
в подзапрос и использовать этот результат для фильтрации analytics
в основном запросе, который затем выполняет остальные соединения и вычисления на надеюсь, значительно сократит количество записей.
Минимальный подзапрос (примечание: нет соединения, нет суммы, возвращается 10 записей):
SELECT
source,
COUNT(id) AS frequency
FROM analytics
WHERE user_id = 52094
GROUP BY source
ORDER BY frequency DESC
LIMIT 10
Полный запрос с использованием вышеуказанного запроса в качестве подзапроса x
:
SELECT
x.source AS referrer,
x.frequency,
SUM(IF(t.status = 'COMPLETED', 1, 0)) AS sales
FROM
(<subquery here>) x
INNER JOIN analytics a
ON x.source = a.source -- This reduces the number of records
LEFT JOIN transactions t
ON a.id = t.analytics
WHERE a.user_id = 52094 -- We could have several users per source
GROUP BY x.source, x.frequency
ORDER BY x.frequency DESC
Если это не даст ожидаемого повышения производительности, это может быть связано с тем, что MySQL применяет соединения в непредвиденном порядке. Как объясняется здесь: "Есть ли способ заставить порядок выполнения MySQL?" , в этом случае вы можете заменить соединение STRAIGHT_JOIN
.
Ответ 7
Я бы попробовал подзапрос:
SELECT a.source AS referrer,
COUNT(*) AS frequency,
SUM((SELECT COUNT(*) FROM transactions t
WHERE a.id = t.analytics AND t.status = 'COMPLETED')) AS sales
FROM analytics a
WHERE a.user_id = 52094
GROUP BY a.source
ORDER BY frequency DESC
LIMIT 10;
Плюс индексы точно так же, как @Gordon answer: analytics (user_id, id, source) и транзакции (аналитика, статус).
Ответ 8
Только проблема, которую я нахожу в вашем запросе, - это
GROUP BY analytics.source
ORDER BY frequency DESC
из-за этого запроса выполняется fileort с использованием временной таблицы.
Один из способов избежать этого - создать другую таблицу, такую как
CREATE TABLE 'analytics_aggr' (
'source' varchar(45) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
'frequency' int(10) DEFAULT NULL,
'sales' int(10) DEFAULT NULL,
KEY 'sales' ('sales')
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;'
вставлять данные в analytics_aggr, используя следующий запрос
insert into analytics_aggr SELECT
analytics.source AS referrer,
COUNT(analytics.id) AS frequency,
SUM(IF(transactions.status = 'COMPLETED', 1, 0)) AS sales
FROM analytics
LEFT JOIN transactions ON analytics.id = transactions.analytics
WHERE analytics.user_id = 52094
GROUP BY analytics.source
ORDER BY null
Теперь вы можете легко получить данные с помощью
select * from analytics_aggr order by sales desc
Ответ 9
Попробуй это
SELECT
a.source AS referrer,
COUNT(a.id) AS frequency,
SUM(t.sales) AS sales
FROM (Select id, source From analytics Where user_id = 52094) a
LEFT JOIN (Select analytics, case when status = 'COMPLETED' Then 1 else 0 end as sales
From transactions) t ON a.id = t.analytics
GROUP BY a.source
ORDER BY frequency DESC
LIMIT 10
Я предлагаю это, потому что вы сказали "они массивная таблица", но этот sql использует только несколько столбцов. В этом случае, если мы используем встроенный просмотр с требуемыми столбцами, тогда это будет хорошо
Примечание: память также будет играть важную роль здесь. Поэтому подтвердите память перед тем, как выбрать встроенный вид
Ответ 10
Я бы попытался отделить запрос от двух таблиц. Поскольку вам нужны только первые 10 source
, я бы сначала их получил, а затем запросил transactions
в столбце sales
:
SELECT source as referrer
,frequency
,(select count(*)
from transactions t
where t.analytics in (select distinct id
from analytics
where user_id = 52094
and source = by_frequency.source)
and status = 'completed'
) as sales
from (SELECT analytics.source
,count(*) as frequency
from analytics
where analytics.user_id = 52094
group by analytics.source
order by frequency desc
limit 10
) by_frequency
Это может быть также быстрее без distinct
Ответ 11
Я предполагаю, что предикат user_id = 52094 предназначен для иллюстрации, и в приложении выбранный user_id является переменной.
Я также предполагаю, что свойство ACID здесь не очень важно.
(1) Поэтому я буду поддерживать две таблицы реплик только с необходимыми полями (это похоже на индексы, предложенные Владимиром выше) с использованием таблицы утилиты.
CREATE TABLE mv_anal (
'id' int(11) NOT NULL,
'user_id' int(11) DEFAULT NULL,
'source' varchar(45),
PRIMARY KEY ('id')
);
CREATE TABLE mv_trans (
'id' int(11) NOT NULL,
'status' varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
'analytics' int(11) DEFAULT NULL,
PRIMARY KEY ('id')
);
CREATE TABLE util (
last_updated_anal int (11) NOT NULL,
last_updated_trans int (11) NOT NULL
);
INSERT INTO util (0, 0);
Усиление здесь заключается в том, что мы будем читать относительно небольшие прогнозы исходных таблиц - надеюсь, работают кеши уровня ОС и уровня БД, и они не считываются из более медленного вторичного хранилища, а из более быстрой ОЗУ. Это может быть очень большим выигрышем.
Вот как я обновляю две таблицы (ниже транзакция выполняется cron):
-- TRANSACTION STARTS --
INSERT INTO mv_trans
SELECT id, IF (status = 'COMPLETE', 1, 0) AS status, analysis
FROM transactions JOIN util
ON util.last_updated_trans <= transactions.id
UPDATE util
SET last_updated_trans = sub.m
FROM (SELECT MAX (id) AS m FROM mv_trans) sub;
-- TRANSACTION COMMITS --
-- similar transaction for mv_anal.
(2) Теперь я рассмотрю избирательность, чтобы уменьшить время последовательного сканирования. Мне нужно будет построить индекс b-дерева для user_id, source и id (в этой последовательности) на mv_anal.
Примечание: вышесказанное может быть достигнуто только путем создания индекса в таблице аналитики, но для построения такого индекса требуется чтение большой таблицы с 60M строк. Мой метод требует, чтобы индексное здание считывало только очень тонкую таблицу. Таким образом, мы можем чаще настраивать btree (чтобы противостоять проблеме перекоса, поскольку таблица добавляется только).
Именно поэтому я убеждаюсь в том, что высокая избирательность достигается при запросе и противодействии проблеме перекоса btree.
(3) В PostgreSQL с подзапросами всегда материализованы. Я также надеюсь на MySQL. Поэтому в качестве последней мили оптимизации:
WITH sub_anal AS (
SELECT user_id, source AS referrer, COUNT (id) AS frequency
FROM mv_anal
WHERE user_id = 52094
GROUP BY user_id, source
ORDER BY COUNT (id) DESC
LIMIT 10
)
SELECT sa.referrer, sa.frequency, SUM (status) AS sales
FROM sub_anal AS sa
JOIN mv_anal anal
ON sa.referrer = anal.source AND sa.user_id = anal.user_id
JOIN mv_trans AS trans
ON anal.id = trans.analytics
Ответ 12
Поздно к вечеринке. Я думаю, вам нужно будет загрузить один индекс в кеш MySQL. NLJ, вероятно, убивает производительность. Вот как я это вижу:
Путь
Ваш запрос прост. Он имеет две таблицы и "путь" очень ясен:
- Оптимизатор должен сначала планировать чтение
analytics
таблицы. - Оптимизатор должен планировать чтение второй таблицы
transactions
. Это связано с тем, что вы используете LEFT OUTER JOIN
. Об этом мало обсуждается. - Кроме того, таблица
analytics
составляет 60 миллионов строк, и лучший путь должен фильтровать строки как можно скорее на этом.
Доступ
Когда путь будет понятным, вам нужно решить, хотите ли вы использовать индексный доступ или доступ к таблице. У обоих есть плюсы и минусы. Однако вы хотите улучшить производительность SELECT
:
- Вы должны выбрать Index Access.
- Избегайте гибридного доступа. Поэтому вам следует избегать любого доступа к таблице (выборки) любой ценой. Перевод: поместите все участвующие столбцы в индексы.
Фильтрация
Опять же, вы хотите высокую производительность для SELECT
. Следовательно:
- Вы должны выполнить фильтрацию на уровне индекса, а не на уровне таблицы.
Агрегация строк
После фильтрации следующим шагом будет объединение строк по GROUP BY analytics.source
. Это можно улучшить, поставив source
столбец в качестве первого столбца в индексе.
Оптимальные индексы для пути, доступа, фильтрации и агрегирования
Учитывая все вышесказанное, вы должны включить все упомянутые столбцы в индексы. Следующие индексы должны улучшить время отклика:
create index ix1_analytics on analytics (user_id, source, id);
create index ix2_transactions on transactions (analytics, status);
Эти индексы соответствуют стратегиям "путь", "доступ" и "фильтрация", описанным выше.
Кэш индекса
Наконец - и это очень важно - загрузите вторичный индекс в кеш памяти MySQL. MySQL выполняет NLJ (вложенный Loop Join) - "ref" в MySQL lingo - и ему нужно получить доступ к второму случайному почти в 200k раз.
К сожалению, я точно не знаю, как загрузить индекс в кеш MySQL. Использование FORCE
может работать, как в:
SELECT
analytics.source AS referrer,
COUNT(analytics.id) AS frequency,
SUM(IF(transactions.status = 'COMPLETED', 1, 0)) AS sales
FROM analytics
LEFT JOIN transactions FORCE index (ix2_transactions)
ON analytics.id = transactions.analytics
WHERE analytics.user_id = 52094
GROUP BY analytics.source
ORDER BY frequency DESC
LIMIT 10
Убедитесь, что у вас достаточно свободного места в кеше. Вот короткий вопрос/ответ, чтобы выяснить: как выяснить, полностью ли индексируется индекс mysql в памяти
Удачи! О, и опубликуйте результаты.
Ответ 13
Этот вопрос определенно получил много внимания, поэтому я уверен, что все очевидные решения были опробованы. Однако я не видел что-то, что обращается к LEFT JOIN
в запросе.
Я заметил, что операторы LEFT JOIN
обычно заставляют планировщиков запросов в хеш-соединение, которые быстро выполняются для небольшого количества результатов, но ужасно медленны для большого количества результатов. Как отмечено в ответе @Rick James, поскольку объединение в исходном запросе находится в поле identity analytics.id
, это приведет к большому числу результатов. Присоединение хэша принесет ужасные результаты. Приведенное ниже предложение относится к этому ниже без каких-либо изменений схемы или обработки.
Поскольку агрегирование осуществляется с помощью analytics.source
, я бы попробовал запрос, который создает отдельные агрегаты для частоты по источникам и продажам по источникам и откладывает левое соединение до тех пор, пока агрегация не будет завершена. Это должно позволить лучше использовать индексы (обычно это объединение слияния для больших наборов данных).
Вот мое предложение:
SELECT t1.source AS referrer, t1.frequency, t2.sales
FROM (
-- Frequency by source
SELECT a.source, COUNT(a.id) AS frequency
FROM analytics a
WHERE a.user_id=52094
GROUP BY a.source
) t1
LEFT JOIN (
-- Sales by source
SELECT a.source,
SUM(IF(t.status = 'COMPLETED', 1, 0)) AS sales
FROM analytics a
JOIN transactions t
WHERE a.id = t.analytics
AND t.status = 'COMPLETED'
AND a.user_id=52094
GROUP by a.source
) t2
ON t1.source = t2.source
ORDER BY frequency DESC
LIMIT 10
Надеюсь это поможет.