Зачем MySQL делать последовательный синхронный ввод / вывод?

8

Рассматривая особенно раздражающий запрос к таблицам MyISAM, который несколько раз занимал много времени, я заметил, что MySQL, похоже, демонстрирует довольно странный шаблон ввода-вывода: при выполнении одного запроса и необходимости выполнения значительного количество операций ввода-вывода (например, для сканирования таблицы или когда кэш-память пуста в результате того, echo 3 > /proc/sys/vm/drop_cachesчто индексы должны быть сначала загружены с диска), размер очереди для базового блочного устройства близок к значению 1, с ужасающей производительностью всего 4-5 МБ / с:

root@mysql-test:~# iostat -xdm 5 /dev/sda
Linux 3.2.0-40-generic (mysql-test)  04/30/2014      _x86_64_        (4 CPU)

Device:         rrqm/s   wrqm/s     r/s     w/s    rMB/s    wMB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
sda               0.14    24.82   18.26   88.79     0.75     4.61   102.56     2.83   26.39   19.29   27.85   2.46  26.31

Device:         rrqm/s   wrqm/s     r/s     w/s    rMB/s    wMB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
sda               0.00    69.29  151.52   72.73     5.31     0.59    53.95     1.21    5.39    7.84    0.29   4.39  98.51

Device:         rrqm/s   wrqm/s     r/s     w/s    rMB/s    wMB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
sda               0.00   153.06  144.29  174.69     4.96     1.36    40.54     1.39    4.36    8.91    0.60   3.15 100.49

Device:         rrqm/s   wrqm/s     r/s     w/s    rMB/s    wMB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
sda               0.00   105.75  150.92  109.03     4.53     0.85    42.41     1.29    4.96    8.15    0.54   3.90 101.36

Device:         rrqm/s   wrqm/s     r/s     w/s    rMB/s    wMB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
sda               0.00    48.89  156.36   51.72     5.28     0.76    59.38     1.28    6.16    8.02    0.55   4.77  99.23

Хотя 150 операций ввода-вывода в секунду просто являются тем, что может обеспечить один диск в данной конфигурации с точки зрения случайного ввода-вывода, результат все еще действительно удивляет меня, так как я ожидаю, что MySQL сможет выполнять асинхронный ввод-вывод для чтения и извлечения большое количество блоков одновременно вместо того, чтобы читать и оценивать их один за другим, фактически игнорируя преимущества распараллеливания, доступные в конфигурациях RAID. Какое проектное решение или вариант конфигурации отвечает за это? Это проблема для конкретной платформы?

Несмотря на то, что я проверил это с таблицами MyISAM большого размера, я вижу аналогичные эффекты с теми же таблицами, преобразованными в InnoDB (хотя и не так плохо, пример запроса по-прежнему занимает 20-30 секунд, при этом большая часть времени уходит на чтение диска с длина очереди 1) после перезапуска демона mysql и поэтому пулы буферов пусты. Я также проверил, что та же проблема сохраняется на 5,6 ГА и текущем 5,7 этапе 14 - пока я использую один поток запросов, MySQL, похоже, не может распараллелить операции ввода-вывода, необходимые для обработки запросов.


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

SELECT herp.id, herp.firstname, herp.lastname, derp.label, herp.email, 
(SELECT CONCAT(label, " (", zip_code, " ", city,")" ) FROM subsidiaries WHERE subsidiaries.id=herp.subsidiary_id ) AS subsidiary, 
(SELECT COUNT(fk_herp) from herp_missing_data WHERE fk_herp=herp.id) AS missing_data
FROM herp LEFT JOIN derp ON derp.id=herp.fk_derp
WHERE (herp.fk_pools='123456')  AND herp.city LIKE '%Some City%' AND herp.active='yes' 
ORDER BY herp.id desc LIMIT 0,10;

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

В используемых таблицах есть куча данных:

mysql> select table_name, engine, table_rows, data_length, index_length from information_schema.tables WHERE tables.TABLE_SCHEMA = 'mydb' and tables.table_name in ( 'herp', 'derp', 'missing_data', 'subsidiaries');
+-------------------------+--------+------------+-------------+--------------+
| table_name              | engine | table_rows | data_length | index_length |
+-------------------------+--------+------------+-------------+--------------+
| derp                    | MyISAM |      14085 |     1118676 |       165888 |
| herp                    | MyISAM |     821747 |   828106512 |    568057856 |
| missing_data            | MyISAM |    1220186 |    15862418 |     29238272 |
| subsidiaries            | MyISAM |       1499 |     6490308 |       103424 |
+-------------------------+--------+------------+-------------+--------------+
4 rows in set (0.00 sec)

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

Профиль для примера выполнения запроса (который в этом примере занял 1 мин 9,17 с) выглядит следующим образом:

mysql> show profile for query 1;
+--------------------------------+-----------+
| Status                         | Duration  |
+--------------------------------+-----------+
| starting                       |  0.000118 |
| Waiting for query cache lock   |  0.000035 |
| init                           |  0.000033 |
| checking query cache for query |  0.000399 |
| checking permissions           |  0.000077 |
| checking permissions           |  0.000030 |
| checking permissions           |  0.000031 |
| checking permissions           |  0.000035 |
| Opening tables                 |  0.000158 |
| init                           |  0.000294 |
| System lock                    |  0.000056 |
| Waiting for query cache lock   |  0.000032 |
| System lock                    |  0.000116 |
| optimizing                     |  0.000063 |
| statistics                     |  0.001964 |
| preparing                      |  0.000104 |
| Sorting result                 |  0.000033 |
| executing                      |  0.000030 |
| Sending data                   |  2.031349 |
| optimizing                     |  0.000054 |
| statistics                     |  0.000039 |
| preparing                      |  0.000024 |
| executing                      |  0.000013 |
| Sending data                   |  0.000044 |
| optimizing                     |  0.000017 |
| statistics                     |  0.000021 |
| preparing                      |  0.000019 |
| executing                      |  0.000013 |
| Sending data                   | 21.477528 |
| executing                      |  0.000070 |
| Sending data                   |  0.000075 |
| executing                      |  0.000027 |
| Sending data                   | 45.692623 |
| end                            |  0.000076 |
| query end                      |  0.000036 |
| closing tables                 |  0.000109 |
| freeing items                  |  0.000067 |
| Waiting for query cache lock   |  0.000038 |
| freeing items                  |  0.000080 |
| Waiting for query cache lock   |  0.000044 |
| freeing items                  |  0.000037 |
| storing result in query cache  |  0.000033 |
| logging slow query             |  0.000103 |
| cleaning up                    |  0.000073 |
+--------------------------------+-----------+
44 rows in set, 1 warning (0.00 sec)
syneticon-ди-джей
источник
У вас есть повторяемый (в идеале простой) тестовый пример, который вы могли бы объяснить более подробно? Например, запрос, который генерирует это поведение? При каких обстоятельствах? Вы пошли по этому пути с "echo 3> ..." & "restart mysql daemon", но не вдавались в подробности.
Скотт Лидли
@ScottLeadley, спасибо, что заглянули в это. Я не думаю, что смогу сделать это «простым» - проблема будет заметна только в том случае, если для одного запроса потребуется считывание большого объема данных, и это будет в основном случайный ввод-вывод. Таблицы и запросы достаточно просты, и хотя я мог публиковать тексты DDL и запросов, я сомневаюсь, что кто-нибудь сможет воспроизвести его сразу, если данные таблиц / индексов не вырастут до сотен мегабайт.
syneticon-dj
Как вы упоминали, ожидание чтения в течение 5 мс соответствует средней задержке вращения одного диска 5400 об / мин. Это может привести к конфликту при чтении «большого количества данных ... в основном случайных операций ввода-вывода». Что касается RAID, вы упомянули об этом, но не дали никаких подробностей об этой конкретной конфигурации.
Скотт Лидли
Не уверен, что могу помочь вам напрямую, потому что я не запускаю ваш конфиг. Но практическое правило StackExchange заключается в том, что действительно хороший вопрос привлекает больше внимания, чем щедрость. Написание идеального вопроса
Скотт Лидли
@ ScottLeadley 5 мс ожидают в основном из-за задержки используемой системы хранения. Я проверил это в различных сценариях - от простого 4-х дискового RAID10 до многоуровневого хранилища с 16-дисковой полкой и поддержкой SSD, результаты последовательно показывают, что нагрузка ввода-вывода не распараллелена и, следовательно, не связана с задержкой. Что я чувствую в корне неправильно. Я добавил детали запроса к этому вопросу, но я еще не уверен, что они окажут большую помощь.
syneticon-dj

Ответы:

8

Сначала позвольте мне уточнить, подтвердив, что MyISAM не выполняет асинхронный ввод-вывод, но InnoDB делает и будет по умолчанию из MySQL 5.5. До 5.5 он использовал «имитацию AIO» с использованием рабочих потоков.

Я думаю, что также важно различать три ситуации:

  1. Несколько запросов, выполняющихся одновременно
  2. Один запрос выполняется параллельно
  3. Какое-то логическое чтение вперед для сканирования таблицы / очистки случаев, когда следующие страницы хорошо известны.

Для (1) I / O сможет выполнять параллельно для этого. У MyISAM есть некоторые ограничения: блокировка таблиц и глобальная блокировка key_buffer(индексный кеш). InnoDB в MySQL 5.5+ действительно светит здесь.

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

Для (3) InnoDB имеет линейное упреждающее чтение для полного чтения (группа из 64 страниц), если читается> 56 страниц (это настраивается), но есть место для дальнейшего улучшения. Facebook написал о внедрении логического чтения в их ветке (с 10-кратным увеличением производительности при сканировании таблиц).

Морган Токер
источник
Спасибо, это дает мне дополнительное понимание того, что я вижу. Означает ли это, что MyISAM не может использовать IOPS более чем на один диск для однопоточной загрузки? Я не могу найти никаких ссылок на это в документации - у вас есть что-нибудь полезное?
Syneticon-DJ
Да. Я не могу думать о месте в документах, где это будет.
Морган Токер
2

Я надеюсь, что missing_dataэто не MyISAM, потому что пустая таблица MyISAM обычно имеет 1024 байта .MYI. От MyISAM ожидается ненулевой размер байта. Нулевой байт .MYIзвучит немного жутко для меня.

Если вы запустите этот запрос метаданных

select table_name, table_rows, data_length, index_length, engine
from information_schema.tables
WHERE tables.TABLE_SCHEMA = 'mydb'
and tables.table_name = 'missing_data';

и движок этой таблицы - MyISAM, вам нужно починить его.

Боковое примечание: если engineесть NULL, это вид. Если это представление или это не MyISAM, пожалуйста, проигнорируйте оставшуюся часть моего поста и добавьте эту информацию к вопросу. Если таблица MyISAM, читайте дальше ...

Согласно вашему запросу метаданных, missing_data.MYDэто около 46M.

Во-первых, запустите это

SHOW CREATE TABLE mydb.missing_data\G

Вы получите либо описание таблицы, либо сообщение об ошибке, говорящее что-то вроде

ERROR 126 (HY000): Incorrect key file for table ...

Если вы получили описание таблицы и это MyISAM, пожалуйста, запустите

OPTIMIZE TABLE mydb.missing_data;

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

REPAIR TABLE mydb.missing_data;

Это должно восстановить страницы индекса для MyISAM.

На всякий случай (если вы используете MySQL 5.6), запустите это после восстановления

FLUSH TABLES mydb.missing_data;

Ваш вопрос

Индексы вашей таблицы могут быть не загружены в память, если MySQL Query Optimizer решит не использовать. Если ваше предложение WHERE диктует, что из индексов должно быть прочитано значительное количество строк, MySQL Query Optimizer увидит это при построении плана EXPLAIN и решит использовать вместо этого полное сканирование таблицы.

Параллельные операции ввода-вывода в таблице MyISAM недостижимы, поскольку не поддаются настройке.

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

RolandoMySQLDBA
источник
Я должен подчеркнуть это еще раз: если mydb.missing_data имеет значение MyISAM и имеет нулевой индекс байтов, значит, что-то определенно не так.
RolandoMySQLDBA
Я обновил данные, чтобы они стали более связными - теперь они показывают результаты только для MyISAM с одного хоста, чтобы люди не запутались.
syneticon-dj