оптимизировать запрос подсчета mysql - PullRequest
13 голосов
/ 09 декабря 2010

Есть ли способ оптимизировать это дальше, или я должен быть уверен, что для подсчета 11M строк требуется 9 секунд?

devuser@xcmst > mysql --user=user --password=pass -D marctoxctransformation -e "desc record_updates"                                                                    
+--------------+----------+------+-----+---------+-------+
| Field        | Type     | Null | Key | Default | Extra |
+--------------+----------+------+-----+---------+-------+
| record_id    | int(11)  | YES  | MUL | NULL    |       | 
| date_updated | datetime | YES  | MUL | NULL    |       | 
+--------------+----------+------+-----+---------+-------+
devuser@xcmst > date; mysql --user=user --password=pass -D marctoxctransformation -e "select count(*) from record_updates where date_updated > '2009-10-11 15:33:22' "; date                         
Thu Dec  9 11:13:17 EST 2010
+----------+
| count(*) |
+----------+
| 11772117 | 
+----------+
Thu Dec  9 11:13:26 EST 2010
devuser@xcmst > mysql --user=user --password=pass -D marctoxctransformation -e "explain select count(*) from record_updates where date_updated > '2009-10-11 15:33:22' "      
+----+-------------+----------------+-------+--------------------------------------------------------+--------------------------------------------------------+---------+------+----------+--------------------------+
| id | select_type | table          | type  | possible_keys                                          | key                                                    | key_len | ref  | rows     | Extra                    |
+----+-------------+----------------+-------+--------------------------------------------------------+--------------------------------------------------------+---------+------+----------+--------------------------+
|  1 | SIMPLE      | record_updates | index | idx_marctoxctransformation_record_updates_date_updated | idx_marctoxctransformation_record_updates_date_updated | 9       | NULL | 11772117 | Using where; Using index | 
+----+-------------+----------------+-------+--------------------------------------------------------+--------------------------------------------------------+---------+------+----------+--------------------------+
devuser@xcmst > mysql --user=user --password=pass -D marctoxctransformation -e "show keys from record_updates"
+----------------+------------+--------------------------------------------------------+--------------+--------------+-----------+-------------+----------+--------+------+------------+---------+
| Table          | Non_unique | Key_name                                               | Seq_in_index | Column_name  | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+----------------+------------+--------------------------------------------------------+--------------+--------------+-----------+-------------+----------+--------+------+------------+---------+
| record_updates |          1 | idx_marctoxctransformation_record_updates_date_updated |            1 | date_updated | A         |        2416 |     NULL | NULL   | YES  | BTREE      |         | 
| record_updates |          1 | idx_marctoxctransformation_record_updates_record_id    |            1 | record_id    | A         |    11772117 |     NULL | NULL   | YES  | BTREE      |         | 
+----------------+------------+--------------------------------------------------------+--------------+--------------+-----------+-------------+----------+--------+------+------------+---------+

ОБНОВЛЕНИЕ - мое решение здесь: http://code.google.com/p/xcmetadataservicestoolkit/wiki/ResumptionToken

Ответы [ 10 ]

20 голосов
/ 12 декабря 2010

Если mysql должен посчитать 11M строк, на самом деле не так уж много способов ускорить простой подсчет.По крайней мере, чтобы не получить его до скорости менее 1 секунды.Вы должны переосмыслить, как вы считаете.Несколько идей:

  1. Добавить поле автоинкремента в таблицу.Похоже, вы не будете удалять из таблицы, поэтому вы можете использовать простую математику, чтобы найти количество записей.Выберите минимальное число автоматического приращения для начальной более ранней даты и максимальное для более поздней даты и вычтите одно из другого, чтобы получить количество записей.Например:

    SELECT min(incr_id) min_id FROM record_updates WHERE date_updated BETWEEN '2009-10-11 15:33:22' AND '2009-10-12 23:59:59';
    SELECT max(incr_id) max_id FROM record_updates WHERE date_updated > DATE_SUB(NOW(), INTERVAL 2 DAY);`
    
  2. Создайте еще одну таблицу, суммирующую количество записей за каждый день.Затем вы можете запросить эту таблицу для общего количества записей.Всего будет 365 записей за каждый год.Если вам нужно перейти к более точным временам, запросите сводную таблицу для полных дней, а текущую таблицу - просто для количества записей для начального и конечного дней.Затем сложите их все вместе.

Если данные не изменяются, что, по-видимому, не так, тогда сводные таблицы будет легко поддерживать и обновлять.Они значительно ускорят процесс.

5 голосов
/ 17 декабря 2010

Поскольку >'2009-10-11 15:33:22' содержит большинство записей,
Я бы предложил сделать обратное сопоставление, например <'2009-10-11 15:33:22' (mysql работает меньше сложнее и требует меньше строк)

select 
  TABLE_ROWS -
  (select count(*) from record_updates where add_date<"2009-10-11 15:33:22") 
from information_schema.tables 
where table_schema = "marctoxctransformation" and table_name="record_updates"

Вы можете комбинироватьс языком программирования (например, оболочкой bash)
, чтобы сделать этот расчет немного более умным ...
, например сначала выполнить план выполнения, чтобы рассчитать, какое сравнение будет использовать меньшую строку

Из моего тестирования (около 10 млн.записи), нормальное сравнение занимает около 3 с,
и теперь сокращается до 0,25 с

2 голосов
/ 17 декабря 2010

MySQL не «оптимизирует» запросы count (*) в InnoDB из-за управления версиями. Каждый элемент в индексе должен быть повторен и проверен, чтобы убедиться, что версия верна для отображения (например, не открытая фиксация). Так как любые ваши данные могут быть изменены по всей базе данных, выборки на расстоянии и кэширование не будут работать. Тем не менее, вы можете получить с помощью триггеров. Есть два метода этого безумия.

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

Если вам не нужны точные цифры, проверьте этот запрос:

select table_rows from information_schema.tables
where table_name = 'foo';

Пример разницы: count (*): 1876668, table_rows: 1899004. Значение table_rows является оценочным, и вы будете получать разные числа каждый раз, даже если ваша база данных не изменяется.

Для моего собственного любопытства: вам нужны точные цифры, которые обновляются каждую секунду? Если так, почему?

2 голосов
/ 13 декабря 2010

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

Например, если предположить, что старые данные редко / никогда не изменяются, но последние данные таковы, создайте сводную таблицу за месяц, заполненную за предыдущий месяц в конце каждого месяца (например, вставьте счетчик января конец февраля). Если у вас есть сводная таблица, вы можете суммировать полные месяцы и месяцы неполадок в начале и в конце диапазона:

select count(*) 
from record_updates 
where date_updated >= '2009-10-11 15:33:22' and date_updated < '2009-11-01';

select count(*) 
from record_updates 
where date_updated >= '2010-12-00';

select sum(row_count) 
from record_updates_summary 
where date_updated >= '2009-11-01' and date_updated < '2010-12-00';

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

select ( select count(*)
         from record_updates 
         where date_updated >= '2010-12-00'
               or ( date_updated>='2009-10-11 15:33:22' 
                    and date_updated < '2009-11-01' ) ) +
       ( select count(*) 
         from record_updates 
         where date_updated >= '2010-12-00' );

Вы можете адаптировать этот подход для составления сводной таблицы на основе целых недель или целых дней.

2 голосов
/ 09 декабря 2010

Вы должны добавить индекс в поле date_updated.

Еще одна вещь, которую вы можете сделать, если не возражаете против изменения структуры таблицы, - это использовать метку времени даты в 'int'вместо формата datetime, и это может быть даже быстрее.Если вы решите сделать это, запрос будет

select count(date_updated) from record_updates where date_updated > 1291911807
1 голос
/ 14 декабря 2010

Есть несколько деталей, которые я бы хотел уточнить (можно было бы добавить в комментарии вопросник q, но на самом деле его легче удалить при обновлении вопроса).

  1. Чтоэто предполагаемое использование данных, один раз вставить и получить количество раз, или ваши вставки и выборки примерно равны?
  2. Вам небезразлична производительность вставки / обновления?
  3. Что такоедвигатель используется для стола?(черт возьми, вы можете сделать SHOW CREATE TABLE ...)
  4. Вам нужен точный или приблизительно точный счет (например, 0,1% правильный)
  5. Можно ли использовать триггеры, сводные таблицы, изменениясхема, изменение СУБД и т. д. или просто добавление / удаление индексов?
  6. Может быть, вам следует объяснить также, какой должна быть эта таблица?У вас есть record_id с количеством элементов, которое соответствует количеству строк, так что это PK или FK или что?Кроме того, количество элементов date_updated предполагает (хотя и не обязательно корректно), что оно имеет одинаковые значения в среднем для ~ 5000 записей), так что же это?- нормально задавать вопрос настройки SQL без контекста, но также неплохо иметь некоторый контекст, особенно если возможен редизайн.

А пока я предлагаю вамполучите этот скрипт настройки и проверьте рекомендации, которые он вам даст (это всего лишь общий скрипт настройки - но он проверит ваши данные и статистику).

1 голос
/ 12 декабря 2010

Если вам нужно вернуть общее количество строк в таблице, есть альтернатива оператору SELECT COUNT(*), который вы можете использовать.SELECT COUNT(*) выполняет полное сканирование таблицы, чтобы получить общее количество строк в таблице, поэтому это может занять много времени.В этом случае вы можете использовать системную таблицу sysindexes .В таблице sysindexes есть столбец ROWS .Этот столбец содержит общее количество строк для каждой таблицы в вашей базе данных.Таким образом, вы можете использовать следующий оператор выбора вместо SELECT COUNT(*):

SELECT rows FROM sysindexes WHERE id = OBJECT_ID('table_name') AND indid < 2

. Это может повысить скорость вашего запроса.* РЕДАКТИРОВАТЬ: Я обнаружил, что мой ответ будет правильным, если вы используете базу данных SQL Server.Базы данных MySQL не имеют таблицы sysindexes.

1 голос
/ 09 декабря 2010

В вашей таблице нет первичного ключа. Возможно, что в этом случае он всегда сканирует всю таблицу. Наличие первичного ключа никогда не является плохой идеей.

0 голосов
/ 14 декабря 2010

Это зависит от нескольких вещей, но что-то вроде этого может работать на вас

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

count1 = "select count(*) from record_updates where date_updated <= '2009-10-11 15:33:22'"

дает вам общее количество записей в таблице, это приблизительное значение в таблице innodb, поэтому ОСТОРОЖНО, зависит от двигателя

count2 = "select table_rows from information_schema.`TABLES` where table_schema = 'marctoxctransformation' and TABLE_NAME = 'record_updates'"

ваш ответ

результат = количество2 - число1

0 голосов
/ 12 декабря 2010

Вместо того, чтобы делать count (*), попробуйте выполнить count (1), например так: -

select count(1) from record_updates where date_updated > '2009-10-11 15:33:22'

Я уже посещал урок DB2 и помню, как инструктор упоминал о подсчете (1) когда мы просто хотим посчитать количество строк в таблице независимо от данных, потому что это технически быстрее, чем count (*).Дайте мне знать, если это что-то меняет.

ПРИМЕЧАНИЕ. Вот ссылка, которую вам может быть интересно прочитать: http://www.mysqlperformanceblog.com/2007/04/10/count-vs-countcol/

...