Оптимизировать SQL-запрос на большой таблице - PullRequest
3 голосов
/ 22 сентября 2008

Прежде всего, этот вопрос касается MySQL 3.23.58, поэтому имейте в виду.

У меня есть 2 таблицы со следующим определением:

Table A: id INT (primary), customer_id INT, offlineid INT

Table B: id INT (primary), name VARCHAR(255)

Теперь в таблице A содержится более 65 тыс. Записей, а в таблице B - ~ 40 записей. В дополнение к 2 индексам первичного ключа, есть также индекс для поля offlineid в таблице A. В каждой таблице есть больше полей, но они не релевантны (как я вижу, спрашивайте при необходимости ) для этого запроса.

Мне впервые был представлен следующий запрос ( время запроса: ~ 22 секунды ):

SELECT b.name, COUNT(*) AS orders, COUNT(DISTINCT(a.kundeid)) AS leads
FROM katalogbestilling_katalog a, medie b
WHERE a.offlineid = b.id
GROUP BY b.name

Теперь каждый идентификатор в medie связан с другим именем, что означает, что вы можете группировать по идентификатору, а также по имени. Немного тестирования взад и вперед остановили меня на этом ( время запроса: ~ 6 секунд ):

SELECT a.name, COUNT(*) AS orders, COUNT(DISTINCT(b.kundeid)) AS leads
FROM medie a
INNER JOIN katalogbestilling_katalog b ON a.id = b.offline
GROUP BY b.offline;

Есть ли способ развернуть его до «мгновенного» времени (максимум 1 секунда в худшем случае)? Я добавил индекс в offlineid, но помимо этого и перестановки запроса, я не знаю, что делать. Запрос EXPLAIN показывает, что запрос использует файловую краткость (исходный запрос также использовал временные таблицы). Все предложения приветствуются!

Ответы [ 10 ]

1 голос
/ 22 сентября 2008

К сожалению, mysql 3 не поддерживает подзапросы. Я подозреваю, что старая версия в целом является причиной низкой производительности.

1 голос
/ 22 сентября 2008

Я собираюсь догадаться, что ваша главная проблема в том, что вы используете такую ​​старую версию MySQL. Возможно, MySQL 3 не нравится COUNT (DISTINCT ()).

С другой стороны, это может быть просто производительность системы. Сколько у тебя памяти?

Тем не менее, MySQL 3 действительно старый. Я бы, по крайней мере, собрал тестовую систему, чтобы узнать, выполнялась ли новая версия быстрее для этого запроса.

0 голосов
/ 22 сентября 2008

Попробуйте оптимизировать сам сервер. См. этот пост Петра Зайцева для получения информации о самых важных переменных. Некоторые из них относятся к InnoDB, другие для MyISAM. Вы не упомянули, какой движок вы используете, который может быть уместен в этом случае (например, count (*) намного быстрее в MyISAM, чем в InnoDB). Вот еще один пост из того же блога и статья из MySQL Forge

0 голосов
/ 22 сентября 2008

Попробуйте добавить индекс к (offlineid, kundeid)

Я добавил 180 000 строк БС в каталог katalog и 30 000 строк БС в medie (с автономным идентификатором katalog, соответствующим идентификатору medie и несколькими перекрывающимися kundeid, чтобы убедиться, что различный счет работает). Имейте в виду, что это на mysql 5, так что если у вас нет похожих результатов, mysql 3 может быть вашим виновником, но, насколько я помню, mysql 3 должен справиться с этим просто отлично.

Мои таблицы:

CREATE TABLE `katalogbestilling_katalog` (
  `id` int(11) NOT NULL auto_increment,
  `offlineid` int(11) NOT NULL,
  `kundeid` int(11) NOT NULL,
  PRIMARY KEY  (`id`),
  KEY `offline_id` (`offlineid`,`kundeid`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=60001 ;

CREATE TABLE `medie` (
  `id` int(11) NOT NULL auto_increment,
  `name` varchar(255) NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=30001 ;

Мой запрос:

SELECT b.name, COUNT(*) AS orders, COUNT(DISTINCT(a.kundeid)) AS leads
FROM medie b
INNER JOIN katalogbestilling_katalog a ON b.id = a.offlineid
GROUP BY a.offlineid
LIMIT 0 , 30


"Showing rows 0 - 29 (30,000 total, Query took 0.0018 sec)"

И объясните:

id:  1
select_type:    SIMPLE
table: a
type: index
possible_keys:  NULL
key:    offline_id
key_len:    8
ref: NULL
rows: 180000
Extra: Using index

id: 1
select_type:    SIMPLE
table: b
type: eq_ref
possible_keys:  PRIMARY
key:    PRIMARY
key_len:    4
ref: test.a.offlineid
rows: 1
Extra:
0 голосов
/ 22 сентября 2008

Вы можете убедиться, что в каждой таблице определены индексы покрытия. Покрывающий индекс - это просто индекс, в котором каждый столбец, запрашиваемый при выборе или используемый в объединении, включается в индекс. Таким образом, движок должен только прочитать запись индекса и не должен также выполнять поиск соответствующей строки, чтобы получить любые запрошенные столбцы, не включенные в индекс. Я с большим успехом использовал эту технику в Oracle и MS SqlServer.

Глядя на ваш запрос, вы можете попробовать:

один индекс для medie.id, medie.name
один индекс для katalogbestilling_katalog.offlineid, katalogbestilling_katalog.kundeid

Столбцы должны быть определены в этих порядках для индекса. Это имеет значение, может ли индекс использоваться или нет.

Подробнее здесь:

Информация об индексе покрытия

0 голосов
/ 22 сентября 2008

Сколько времени это займет:

SELECT fieldlist FROM A
SELECT fieldlist FROM B

Если ваш mysql работает слишком медленно по объединению, возможно, лучше вывести данные с помощью сканирования одной таблицы и сшить их вместе вне базы данных. 65 тыс. Записей на самом деле не , что много.

0 голосов
/ 22 сентября 2008

Ваш второй запрос в порядке, и строки 65k + 40k не очень велики:)

Поместите новый индекс в столбец katalogbestilling_katalog.offline, и он будет работать быстрее для вас.

0 голосов
/ 22 сентября 2008

Хорошо, если запрос выполняется достаточно часто, чтобы оправдать накладные расходы, создайте индекс для таблицы A, содержащей поля, используемые в запросе. Тогда все результаты можно будет прочитать из индекса, и он не будет сканировать таблицу.

Тем не менее, весь мой опыт основан на MSSQL, поэтому может не работать.

0 голосов
/ 22 сентября 2008

Вы можете получить небольшое увеличение производительности, если вы удалите внутреннее объединение и замените его вложенным оператором выбора, а также удалите счетчик (*) и замените его на PK.

SELECT a.name, COUNT(*) AS orders, COUNT(DISTINCT(b.kundeid)) AS leads FROM medie aINNER JOIN katalogbestilling_katalog b ON a.id = b.offline GROUP BY b.offline;

будет

SELECT a.name, COUNT(a.id) AS orders, (SELECT COUNT(kundeid) FROM katalogbestilling_katalog b WHERE b.offline = a.id) AS Leads FROM medie a;

0 голосов
/ 22 сентября 2008

Как определяется Кундеид? Было бы полезно увидеть полную схему для обеих таблиц (сгенерированных MySQL, т.е. с индексами), а также вывод EXPLAIN с запросами выше.

Самый простой способ отладить это и выяснить, в чем заключается ваше узкое место, - это начать удалять поля один за другим из запроса и измерить, сколько времени потребуется для его выполнения (не забудьте запустить RESET QUERY CACHE перед выполнением каждого запроса). ). В какой-то момент вы увидите значительное сокращение времени выполнения, а затем вы определили свое узкое место. Например:

SELECT b.name, COUNT(*) AS orders, COUNT(DISTINCT(a.kundeid)) AS leads
FROM katalogbestilling_katalog a, medie b
WHERE a.offlineid = b.id
GROUP BY b.name

может стать

SELECT b.name, COUNT(DISTINCT(a.kundeid)) AS leads
FROM katalogbestilling_katalog a, medie b
WHERE a.offlineid = b.id
GROUP BY b.name

, чтобы исключить возможность "заказов", являющихся узким местом, или

SELECT b.name, COUNT(*) AS orders
FROM katalogbestilling_katalog a, medie b
WHERE a.offlineid = b.id
GROUP BY b.name

для устранения "отведений" от уравновешивания. Это приведет вас в правильном направлении.

обновление : я не предлагаю удалять какие-либо данные из окончательного запроса. Просто удалите их, чтобы уменьшить количество переменных при поиске узкого места. Учитывая ваш комментарий, я понимаю

SELECT b.name
FROM katalogbestilling_katalog a, medie b
WHERE a.offlineid = b.id
GROUP BY b.name

все еще плохо работает? Это ясно означает, что это либо соединение, которое не оптимизировано, либо группа по (которую вы можете проверить, удалив группу с помощью - либо JOIN будет все еще медленным, в этом случае это проблема, которую вам нужно решить, или она не будет - в этом случае это, очевидно, GROUP BY). Можете ли вы опубликовать вывод

EXPLAIN SELECT b.name
FROM katalogbestilling_katalog a, medie b
WHERE a.offlineid = b.id
GROUP BY b.name

а также схемы таблиц (чтобы было легче отлаживать)?

обновление # 2

также существует вероятность того, что все ваши индексы созданы правильно, но у вас неправильно настроена установка mysql, когда речь идет о максимальном использовании памяти или что-то в этом духе, что вынуждает ее использовать сортировку диска.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...