Как ускорить «выберите количество (*)» с «группировать по» и «где»? - PullRequest
23 голосов
/ 23 июня 2009

Как ускорить select count(*) с group by?
Это слишком медленно и используется очень часто.
У меня большие проблемы с использованием select count(*) и group by с таблицей, содержащей более 3 000 000 строк.

select object_title,count(*) as hot_num   
from  relations 
where relation_title='XXXX'   
group by object_title  

заголовок_отношения , заголовок_объекта является varchar. где отношение_title = 'XXXX' , которое возвращает более 1 000 000 строк, приводит к тому, что индексы object_title могут работать неправильно.

Ответы [ 8 ]

49 голосов
/ 12 октября 2009

Вот несколько вещей, которые я бы попробовал в порядке возрастания сложности:

(проще) - Убедитесь, что у вас есть правильный индекс покрытия

CREATE INDEX ix_temp ON relations (relation_title, object_title);

Это должно максимизировать производительность при существующей схеме, поскольку (если ваша версия оптимизатора mySQL действительно глупа!), Это минимизирует количество операций ввода-вывода, необходимых для удовлетворения вашего запроса (в отличие от того, если индекс находится в обратном порядке, где весь индекс должен быть отсканирован), и он покроет запрос, так что вам не придется прикасаться к кластерному индексу.

(немного сложнее) - убедитесь, что ваши поля varchar настолько малы, насколько это возможно

Одна из проблем, связанных с индексами varchar в MySQL, заключается в том, что при обработке запроса полный объявленный размер поля помещается в ОЗУ. Таким образом, если у вас есть varchar (256), но вы используете только 4 символа, вы все равно платите 256-байтовое использование оперативной памяти во время обработки запроса. Ой! Так что, если вы можете легко сократить свои пределы varchar, это должно ускорить ваши запросы.

(тяжелее) - нормализовать

30% ваших строк, имеющих одно строковое значение, - это чистый крик для нормализации в другую таблицу, чтобы вы не дублировали строки миллионы раз. Рассмотрите возможность нормализации в три таблицы и использования целочисленных идентификаторов для их объединения.

В некоторых случаях вы можете нормализовать под прикрытиями и скрыть нормализацию с представлениями, которые соответствуют имени текущей таблицы ... тогда вам нужно только сделать так, чтобы ваши запросы INSERT / UPDATE / DELETE знали о нормализации, но могут оставить ваш выбор один.

(самое сложное) - хэшируйте ваши строковые столбцы и индексируйте хэши

Если нормализация означает изменение слишком большого количества кода, но вы можете немного изменить свою схему, вы можете рассмотреть возможность создания 128-битных хэшей для ваших строковых столбцов (используя функцию MD5 ). В этом случае (в отличие от нормализации) вам не нужно изменять все ваши запросы, только INSERT и некоторые из SELECT. В любом случае вы захотите хэшировать свои строковые поля, а затем создать индекс для хешей, например,

CREATE INDEX ix_temp ON relations (relation_title_hash, object_title_hash);

Обратите внимание, что вам нужно поэкспериментировать с SELECT, чтобы убедиться, что вы выполняете вычисления с помощью хеш-индекса, а не извлекаете кластеризованный индекс (необходим для разрешения фактического текстового значения object_title, чтобы удовлетворить запрос ).

Кроме того, если отношение_титла имеет небольшой размер varchar, но заголовок объекта имеет большой размер, то вы можете потенциально хэшировать только объект_титл и создать индекс для (relation_title, object_title_hash).

Обратите внимание, что это решение помогает, только если одно или оба из этих полей очень длинные относительно размера хэшей.

Также обратите внимание, что при хешировании есть интересные влияния на чувствительность к регистру / сопоставлению, поскольку хеш строчной строки не совпадает с хешем заглавной. Поэтому вам необходимо убедиться, что вы применяете канонизацию к строкам перед их хэшированием - другими словами, используйте только хеш-регистр, если вы находитесь в БД без учета регистра. Вы также можете обрезать пробелы с начала или до конца, в зависимости от того, как ваша БД обрабатывает начальные / конечные пробелы.

9 голосов
/ 23 июня 2009

Индексирование столбцов в предложении GROUP BY будет первым делом с использованием составного индекса. На такой запрос потенциально можно ответить, используя только индексные данные, избегая необходимости сканировать таблицу вообще. Поскольку записи в индексе отсортированы, СУБД не должна выполнять отдельную сортировку как часть групповой обработки. Однако индекс замедлит обновления таблицы, поэтому будьте осторожны с этим, если в вашей таблице происходят значительные обновления.

Если вы используете InnoDB для хранения таблицы, строки таблицы будут физически сгруппированы по индексу первичного ключа. Если это (или его лидирующая часть) совпадает с вашим ключом GROUP BY, это должно ускорить такой запрос, потому что связанные записи будут получены вместе. Опять же, это избавляет от необходимости выполнять отдельную сортировку.

В общем, растровые индексы были бы другой эффективной альтернативой, но MySQL в настоящее время не поддерживает их, насколько я знаю.

Материализованное представление было бы другим возможным подходом, но опять же, это не поддерживается непосредственно в MySQL. Однако, если вам не требуется, чтобы статистика COUNT была полностью обновленной, вы можете периодически запускать оператор CREATE TABLE ... AS SELECT ... для ручного кэширования результатов. Это немного некрасиво, поскольку оно не прозрачно, но может быть приемлемо в вашем случае.

Вы также можете поддерживать таблицу кеша логического уровня, используя триггеры. В этой таблице будет столбец для каждого столбца в предложении GROUP BY, а также столбец Count для хранения количества строк для этого конкретного значения ключа группировки. Каждый раз, когда строка добавляется или обновляется в базовой таблице, вставляйте или увеличивайте / уменьшайте строку счетчика в сводной таблице для этого конкретного ключа группировки. Это может быть лучше, чем подход с поддельным материализованным представлением, поскольку кэшированная сводка всегда будет актуальной, а каждое обновление выполняется постепенно и должно оказывать меньшее влияние на ресурсы. Однако я думаю, что вам придется остерегаться конфликта блокировок в таблице кеша.

7 голосов
/ 24 июня 2009

Если у вас есть InnoDB, count (*) и любая другая агрегатная функция выполнят сканирование таблицы. Я вижу несколько решений здесь:

  1. Используйте триггеры и храните агрегаты в отдельной таблице. Плюсы: честность. Минусы: медленные обновления
  2. Использовать очереди обработки. Плюсы: быстрые обновления. Минусы: старое состояние может сохраняться до тех пор, пока очередь не будет обработана, поэтому пользователь может чувствовать недостаток целостности.
  3. Полностью разделите уровень доступа к хранилищу и сохраните агрегаты в отдельной таблице. Уровень хранения будет знать о структуре данных и может применять дельты вместо полных подсчетов. Например, если вы предоставите функциональность «addObject», вы будете знать, когда был добавлен объект, и, следовательно, на совокупность. Тогда вы делаете только update table set count = count + 1. Плюсы: быстрые обновления, целостность (вы можете использовать блокировку, хотя в случае, если несколько клиентов могут изменить одну и ту же запись). Минусы: вы объединяете немного бизнес-логики и хранилища.
2 голосов
/ 12 октября 2009

Я вижу, что несколько человек спросили, какой движок вы использовали для запроса. Я настоятельно рекомендую вам использовать MyISAM по следующим причинам:

InnoDB - @Sorin Mocanu правильно определил, что вы будете выполнять полное сканирование таблицы независимо от индексов.

MyISAM - всегда поддерживает текущий счетчик строк.

Наконец, как сказал @justin, убедитесь, что у вас есть правильный индекс покрытия:

CREATE INDEX ix_temp ON relations (relation_title, object_title);
1 голос
/ 24 июня 2009

тест рассчитывать (myprimaryindexcolumn) и сравните производительность с вашим счетом (*)

0 голосов
/ 17 октября 2009

Я бы предложил архивировать данные, если нет особых причин хранить их в базе данных или вы можете разделить данные и выполнить запросы отдельно.

0 голосов
/ 23 июня 2009

Если у вас размер всей таблицы, вам следует запросить мета таблицы или информационную схему (которые существуют в каждой СУБД, которую я знаю, но я не уверен насчет MySQL). Если ваш запрос избирательный, вы должны убедиться, что для него есть индекс.

AFAIK, больше ничего не поделаешь.

0 голосов
/ 23 июня 2009

есть точка, в которой вы действительно нуждаетесь больше RAM / CPU / IO. Возможно, вы ударили это для вашего оборудования.

Замечу, что индексы обычно неэффективны (если они не покрытие) для запросов, которые достигают более 1-2% от общего числа строк в таблице. Если ваш большой запрос выполняет поиск по индексу и поиск по закладкам, это может быть из-за кэшированного плана, который был из всего запроса за день. Попробуйте добавить в WITH (INDEX = 0), чтобы вызвать сканирование таблицы и посмотреть, быстрее ли это.

взять это из: http://www.microsoft.com/communities/newsgroups/en-us/default.aspx?dg=microsoft.public.sqlserver.programming&tid=4631bab4-0104-47aa-b548-e8428073b6e6&cat=&lang=&cr=&sloc=&p=1

...