Почему InnoDB не хранит количество строк?

19

Всем известно, что в таблицах, которые используют InnoDB в качестве движка, такие запросы SELECT COUNT(*) FROM mytableочень неточны и очень медленны, особенно когда таблица становится больше и когда во время выполнения этого запроса происходят постоянные вставки / удаления строк.

Как я понял, InnoDB не сохраняет количество строк во внутренней переменной, что является причиной этой проблемы.

Мой вопрос: почему это так? Будет ли так сложно хранить такую ​​информацию? Это важная информация, которую нужно знать во многих ситуациях. Единственная трудность, которую я вижу, если такой внутренний счет будет реализован, - это когда транзакции задействованы: если транзакция не совершена, вы считаете количество строк, вставленных ею, или нет?

PS: я не специалист по БД, я просто тот, у кого MySQL простое хобби. Так что, если я просто спросил что-то глупое, не будь чрезмерно критичен: D.

Раду Мурзеа
источник
6
Медленно да Неточно, нет. Это медленно, потому что это дает точный результат. Если у вас есть таблица с 200M строк и, возможно, много других транзакций, которые вставляют / удаляют в одну и ту же таблицу, возможно, много строк в секунду, возникает другой вопрос: «Вам нужно точное число?»
ypercubeᵀᴹ
@ypercube Я знаю, что несколько раз видел в phpmyadmin некоторые значения количества строк, которые были очень отключены. Плюс, там есть комментарий, говорящий что-то вроде "может быть не точным".
Раду Мурзеа
1
@RaduMurzea phpMyAdmin использует альтернативный метод для вычисления количества таблиц для таблиц InnoDB по причинам скорости, о которых вы знаете. Вот где упоминаемая вами неточность входит в игру. Актуальные SELECT COUNT(*) FROM ...запросы точны. Если вы предпочитаете, phpMyAdmin можно настроить так, чтобы он всегда использовал точное количество строк за счет скорости. Дополнительная информация: stackoverflow.com/questions/11926259/…
DOOManiac

Ответы:

9

Я согласен с @RemusRusanu (+1 за его ответ)

SELECT COUNT(*) FROM mydb.mytableв InnoDB ведет себя как механизм хранения транзакций. Сравните это с MyISAM.

MyISAM

Если mydb.mytableэто таблица MyISAM, запуск SELECT COUNT(*) FROM mydb.mytable;аналогичен запуску SELECT table_rows FROM information_schema.table WHERE table_schema = 'mydb' AND table_name = 'mytable';. Это вызывает быстрый поиск количества строк в заголовке таблицы MyISAM.

InnoDB

Если mydb.mytableэто таблица InnoDB, вы получаете мешанину вещей, происходящих. У вас есть MVCC, регулирующий следующее:

  • ib_logfile0 / ib_logfile1 (повтор журналов)
  • ibdata1
    • Отменить Журналы
    • Rollbacks
    • Изменения в словаре данных
  • Управление буферным пулом
  • Изоляция транзакции (4 вида)
    • Повторяемые чтения
    • Чтение совершено
    • Читать незафиксированное
    • Сериализуемый

Запрос InnoDB для подсчета таблицы требует навигации по этим зловещим вещам. На самом деле, никто никогда не узнает, SELECT COUNT(*) from mydb.mytableучитывает ли повторяемое чтение только считанные или непрочитанные чтения.

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

Согласно документации MySQL на innodb_stats_on_meta_data

Когда эта переменная включена (которая используется по умолчанию, как и до ее создания), InnoDB обновляет статистику во время операторов метаданных, таких как SHOW TABLE STATUS или SHOW INDEX, или при доступе к таблицам INFORMATION_SCHEMA TABLES или STATISTICS. (Эти обновления аналогичны тем, что происходят для ANALYZE TABLE.) При отключении InnoDB не обновляет статистику во время этих операций. Отключение этой переменной может повысить скорость доступа к схемам с большим количеством таблиц или индексов. Это также может улучшить стабильность планов выполнения для запросов, которые включают таблицы InnoDB.

Отключение может дать или не дать вам более стабильный счет с точки зрения настройки планов EXPLAIN. Это может повлиять на производительность SELECT COUNT(*) from mydb.mytableлибо в хорошем, либо в плохом смысле, либо не повлиять вообще. Попробуйте и посмотрите !!!

RolandoMySQLDBA
источник
16

Для начала нет такой вещи, как «текущий счетчик» для хранения в переменной. Подобный запрос SELECT COUNT(*) FROM ...зависит от текущего уровня изоляции и всех параллельных ожидающих транзакций. В зависимости от уровня изоляции, запрос может видеть или не видеть строки, вставленные или удаленные в ожидании незавершенных транзакций. Единственный способ ответить - подсчитать строки, которые видны для текущей транзакции.

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

Ремус Русану
источник
1
Итак, это зависит от уровня изоляции, это имеет смысл. Но это все еще может быть реализовано.
Раду Мурзеа
@SoboLAN Есть много причин, почему это не должно и не может быть, большинство из которых перечислены выше. Реализуете ли вы это, поддерживая список подсчетов для каждой таблицы на начало транзакции (независимо от того, какой Oracle есть в MySQL)? Управление таким количеством было бы огромными накладными расходами - подумайте о базе данных с сотнями или тысячами одновременных сеансов, каждый из которых выполняет большое количество INSERT / DELETE для одной и той же таблицы. Невозможно поддерживать.
Philᵀᴹ
Реализовать это довольно сложно. Просто подумайте, что счет должен быть сохранен в БД, это означает, что где-то в метаданных, и этот счет должен поддерживаться каждой транзакцией, которая вставляет или удаляет строку. Как бы вы заблокировали эти метаданные? И как бы вы справились с откатами? Это далеко не тривиально. И результат будет пригоден для очень узкого подмножества запросов.
Ремус Русану
3
@JackDouglas Интересно. Из того, что я видел в прошлом, COUNT(*)запросы редко бывают необходимы в реальности и, как правило, являются результатом неопытности разработчика (считайте строки, прежде чем мы их выберем!) Или плохого дизайна приложения.
Philᵀᴹ
1
@SoboLAN - нет, не будет. Наличие службы, которая обновляет какую-то статистическую таблицу через заданные промежутки времени, намного лучше. Представьте, что у вас есть большая база данных и несколько администраторов, запрашивающих большинство таблиц SELECT COUNT(*), добавьте неоптимизированную WHEREтаблицу, и у вас будет несколько пользователей, которые ставят БД на колени для нескольких сомнительно полезных счетчиков статистики.
NB
0

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

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

Мне редко требуется количество строк для таблицы, хотя я использую COUNT (*) совсем немного. У меня вообще есть предложение WHERE. Используя эффективный индекс для небольшого набора результатов, я считаю, что они достаточно быстрые.

Я не согласен с тем, что подсчет является неточным. Подсчеты представляют собой снимок данных, и я всегда находил их точными.

Короче говоря, MySQL предоставляет вам возможность реализовать это для InnoDB. Вы можете сохранить счетчик и увеличивать / уменьшать его после каждого запроса. Тем не менее, более простым решением, вероятно, является переход на MyISAM.

Маркус Адамс
источник
2
Это не позволяет вести точный подсчет строк в транзакционной системе. Потому что есть столько разных (и правильных) счетчиков строк, сколько активных транзакций.
a_horse_with_no_name
5
Я дал здесь -1 для «Хотя, более простое решение, вероятно, состоит в том, чтобы перейти на MyISAM». Я никогда не рекомендовал бы переключаться на MyISAM просто для того, чтобы получить количество строк.
Дерек Дауни
@a_horse_with_no_name, так что вы соглашаетесь с тем, что для каждой транзакции будет «правильный» счетчик строк. Кажется возможным для меня.
Маркус Адамс
1
@DTest, я никогда не говорил «просто чтобы получить количество строк».
Маркус Адамс
@a_horse_with_no_name, это не так. Конечно , мы только подсчет количества строк , когда сделка получает совершенное право?
Pacerier