Подтвердить что ты не робот

"SELECT COUNT (*)" медленный, даже с предложением where

Я пытаюсь выяснить, как оптимизировать очень медленный запрос в MySQL (я не проектировал это):

SELECT COUNT(*) FROM change_event me WHERE change_event_id > '1212281603783391';
+----------+
| COUNT(*) |
+----------+
|  3224022 |
+----------+
1 row in set (1 min 0.16 sec)

Сравнивая это с полным счетом:

select count(*) from change_event;
+----------+
| count(*) |
+----------+
|  6069102 |
+----------+
1 row in set (4.21 sec)

Объяснение объяснения мне не помогает:

 explain SELECT COUNT(*) FROM change_event me WHERE change_event_id > '1212281603783391'\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: me
         type: range
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 8
          ref: NULL
         rows: 4120213
        Extra: Using where; Using index
1 row in set (0.00 sec)

ОК, он по-прежнему считает, что для подсчета требуется примерно 4 миллиона записей, но я мог бы рассчитывать строки в файле быстрее этого! Я не понимаю, почему MySQL делает это долго.

Здесь определение таблицы:

CREATE TABLE `change_event` (
  `change_event_id` bigint(20) NOT NULL default '0',
  `timestamp` datetime NOT NULL,
  `change_type` enum('create','update','delete','noop') default NULL,
  `changed_object_type` enum('Brand','Broadcast','Episode','OnDemand') NOT NULL,
  `changed_object_id` varchar(255) default NULL,
  `changed_object_modified` datetime NOT NULL default '1000-01-01 00:00:00',
  `modified` datetime NOT NULL default '1000-01-01 00:00:00',
  `created` datetime NOT NULL default '1000-01-01 00:00:00',
  `pid` char(15) default NULL,
  `episode_pid` char(15) default NULL,
  `import_id` int(11) NOT NULL,
  `status` enum('success','failure') NOT NULL,
  `xml_diff` text,
  `node_digest` char(32) default NULL,
  PRIMARY KEY  (`change_event_id`),
  KEY `idx_change_events_changed_object_id` (`changed_object_id`),
  KEY `idx_change_events_episode_pid` (`episode_pid`),
  KEY `fk_import_id` (`import_id`),
  KEY `idx_change_event_timestamp_ce_id` (`timestamp`,`change_event_id`),
  KEY `idx_change_event_status` (`status`),
  CONSTRAINT `fk_change_event_import` FOREIGN KEY (`import_id`) REFERENCES `import` (`import_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

Версия:

$ mysql --version
mysql  Ver 14.12 Distrib 5.0.37, for pc-solaris2.8 (i386) using readline 5.0

Есть ли что-то очевидное, что мне не хватает? (Да, я уже пробовал "SELECT COUNT (change_event_id)", но нет разницы в производительности).

4b9b3361

Ответ 1

InnoDB использует кластерные первичные ключи, поэтому первичный ключ сохраняется вместе с строкой на страницах данных, а не на отдельных страницах индекса. Чтобы выполнить сканирование диапазона, вам все равно придется сканировать все потенциально широкие строки на страницах данных; обратите внимание, что эта таблица содержит столбец TEXT.

Две вещи, которые я хотел бы попробовать:

  • запустите optimize table. Это гарантирует, что страницы данных будут физически сохранены в отсортированном порядке. Возможно, это ускорит сканирование диапазона на кластерном первичном ключе.
  • создайте дополнительный непервичный индекс только для столбца change_event_id. Это сохранит копию этого столбца на страницах индекса, которые будут намного быстрее сканировать. После его создания проверьте план объяснения, чтобы убедиться, что он использует новый индекс.

(вы также, вероятно, хотите, чтобы столбец change_event_id bigint без знака, если он увеличивался с нуля)

Ответ 2

Вот несколько советов, которые я предлагаю:

  • Измените столбец "bigint" на "int unsigned". Вы действительно ожидаете иметь в этой таблице более 4,2 миллиарда записей? Если нет, то вы тратите пространство (и время) на очень широкое поле. Индексы MySQL более эффективны для небольших типов данных.

  • Запустите команду OPTIMIZE TABLE" и посмотрите, будет ли ваш запрос быстрее после этого.

  • Вы также можете рассмотреть разделение таблицы в соответствии с полем идентификатора, особенно если старые записи (с более низкими значениями ID) становятся менее значимыми с течением времени. Секционированная таблица часто может выполнять агрегированные запросы быстрее, чем одна огромная, не разбитая таблица.


EDIT:

Более внимательно рассмотрев эту таблицу, она выглядит как таблица стиля ведения журнала, где строки вставляются, но не изменяются.

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

Ответ 3

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

Ответ 4

Проверьте, насколько фрагментированы ваши индексы. В моей компании у нас есть ночной процесс импорта, который разрушает наши индексы и со временем может оказать глубокое влияние на скорости доступа к данным. Например, у нас была процедура SQL, для которой потребовалось 2 часа для запуска через один день после дефрагментации индексов, которые потребовались 3 минуты. мы используем SQL Server 2005 для поиска script, который может проверить это на MySQL.

Обновление: Посмотрите эту ссылку: http://dev.mysql.com/doc/refman/5.0/en/innodb-file-defragmenting.html

Ответ 5

Запустите "analyze table_name" на этой таблице - возможно, что индексы более не оптимальны.

Вы можете часто говорить об этом, запустив "show index from table_name". Если значение мощности NULL, вам необходимо принудительно провести повторный анализ.

Ответ 6

MySQL действительно говорит "Использовать где" во-первых, так как ему нужно прочитать все записи/значения из данных индекса, чтобы фактически их подсчитать. С InnoDb он также пытается "захватить" этот диапазон записи в 4 мили, чтобы посчитать его.

Возможно, вам придется экспериментировать с разными уровнями изоляции транзакций: http://dev.mysql.com/doc/refman/5.1/en/set-transaction.html#isolevel_read-uncommitted

и посмотрите, какой из них лучше.

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

Ответ 7

Я бы создал таблицу "счетчиков" и добавил триггеры "create row" / "delete row" в таблицу, которую вы подсчитываете. Триггеры должны увеличивать/уменьшать значения счета в таблице "счетчики" на каждой вставке/удалении, поэтому вам не нужно будет вычислять их каждый раз, когда они вам понадобятся.

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

Для некоторых ссылок посмотрите на http://pure.rednoize.com/2007/04/03/mysql-performance-use-counter-tables/