Как ускорить “выбрать количество (*)” с помощью “сгруппировать по” и “где"?

StackOverflow https://stackoverflow.com/questions/1031312

Вопрос

Как ускорить 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  

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

Это было полезно?

Решение

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

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

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, но заголовок объекта имеет большой размер, вы можете потенциально хэшировать только object_title и создать индекс для (lation_title, object_title_hash) .

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

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

Другие советы

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

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

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

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

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

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

<Ол>
  • Используйте триггеры и храните агрегаты в отдельной таблице. Плюсы: честность. Минусы: медленные обновления
  • Использовать очереди обработки. Плюсы: быстрые обновления. Минусы: старое состояние может сохраняться до тех пор, пока очередь не будет обработана, поэтому пользователь может почувствовать недостаток целостности.
  • Полностью разделите уровень доступа к хранилищу и сохраните агрегаты в отдельной таблице. Уровень хранения будет знать о структуре данных и может применять дельты вместо полных подсчетов. Например, если вы указали " addObject " функциональность внутри, что вы будете знать, когда объект был добавлен и, следовательно, на совокупность будет затронут. Затем вы делаете только обновление таблицы set count = count + 1 . Плюсы: быстрые обновления, целостность (вы можете использовать блокировку, хотя в случае, если несколько клиентов могут изменить одну и ту же запись). Минусы: вы объединяете немного бизнес-логики и хранилища.
  • Я вижу, что несколько человек спросили, какой механизм вы использовали для запроса. Я настоятельно рекомендую вам использовать MyISAM по следующим причинам:

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

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

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

    CREATE INDEX ix_temp ON relations (relation_title, object_title);
    

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

    есть точка, в которой вы действительно нуждаетесь больше 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 & амп; кот = & амп; = & языки усилителя; кр = & амп; SLOC = & амп; р = 1

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

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

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

    Лицензировано под: CC-BY-SA с атрибуция
    Не связан с StackOverflow
    scroll top