Pull to refresh

Comments 90

Масштабировать проще, если не используется join.
Зависит от задач… Хотя именно масштабировать проще, я согласен. А вот сохранить при этом высокую производительность — это отдельный вопрос (см. последний пункт к статье, который я в последний момент добавил :))
сохранять высокую производительность тоже проще
тесты немного оторваны от жизни. когда в пуле стоит сотни запросов, то каждый джоин — это выделение доп буферов под промежуточные результаты, запуск нового потока на каждую часть джоина, синхронизация и прочу доп. действий, которые не заметны — если нет нагрузки, нет гонки за ресурсы…
да, в статье получается микрооптимизация…
хотя статья интересна и в каком-то смысле даже позновательна. Спасибо автору за исследовательскую работу.
А табуретки проще выпускать квадратные и без ножек…
Это другое, если подразумевается большая нагрузка об этом лучше сразу подумать, если нагрузка не планируется, конечно, удобнее использовать JOIN-ы.
Как раз делал сравнение PostgreSQL и MongoDb на примере join.
Монго где то на 40-50% быстрее в среднем.
Но вот по Insert проигрывает очень много- от 2-х до 5-ти раз (1к-1М записей).
Возможно, такие результаты связаны еще и с тем, что в качестве оболочки для запросов использовался Python 2.6.

И тем не менее, join (точнее его имитация, ведь как такового join в mongo нет) очень дорогая операция для любой реляционной БД.
А как эмулировали джойн?
Из написанного стало понятно: use the index, Luke
Не всегда возможно поставить индексы по колонкам, по которым производится поиск. К тому же, статья вообще не об этом. В тексте я специально указал, что индексы в данном случае можно было бы использовать, но я ставил целью измерить скорость работы JOIN в случае, если у нас много данных, а не что-то другое.
Я бы согласился. Как обстоит скорость join`ов у разных движков с учётом адекватных индексов?
В данном случае users присоединяется к calls по первичному ключу. Какие бы адекватные индексы вы бы хотели видеть еще?
хотелось-бы видеть индекс как минимум на столбце calls.user_id. не?
Ну и ещё, хотел бы заметить: посмотрите на времена выполнения запросов. И это к таблице с 10 млн записей! По-моему, очевидно, что бывают случаи, когда FULL SCAN будет быстрее, чем, скажем, INDEX MERGE или что-нибудь ещё с участием индекса.
У MySql есть анализатор, который сам определяет такие случаи, и не использует индексы.
Хотя он и может ошибаться, но делает он это реже нас, так что я доверяю.
Кто бы спорил, что он есть :). Но иногда бывает так, что он не понимает, как быстрее будет выполнить запрос, ибо у него просто не может быть какой-нибудь информации о структуре, которая, при этом, может быть у вас.
UFO just landed and posted this here
Если внутри IN подзапрос то индекс не будет использоваться.
dev.mysql.com/doc/refman/5.6/en/in-subquery-optimization.html

Consider the following subquery comparison:

outer_expr IN (SELECT inner_expr FROM… WHERE subquery_where)

MySQL evaluates queries “from outside to inside.” That is, it first obtains the value of the outer expression outer_expr, and then runs the subquery and captures the rows that it produces.
Присоединяюсь к zerkms. Если не сложно, поясните свой коммент. Или хотя бы ссылки на почитать.
По-моему, в этом случае проще всего посмотреть самому, «EXPLAIN SELECT MAX(cost) FROM calls WHERE user_id IN( SELECT id FROM users WHERE sex = 'M' )», не?
Для этого надо создавать таблички и наполнять их данными.
Кроме того, меня не очень интересует конкретный пример, больше теория, почему «Если внутри IN подзапрос то индекс не будет использоваться.»
MySQL перепишет это как JOIN, я проверял :)
Тестирование производительности join без индексов чем-то похоже на тестирование скорости болидов формулы один, с незаведенными двигателями. Самую главную фишку выключили и вперед:)
Без обид, но правда, джоины в первую очередь тем и хороши, что позволяют все сшивать по индексам, а не делать тупой фуллскан. Статья несомненно представляет интерес, особенно в меру сравнения InnoDB и MyISAM, но уж очень теоретический, в меру редкости подобных вещей.

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

p.s.: Конфигов mysql не осталось? Было бы интересно глянуть.
> p.s.: Конфигов mysql не осталось? Было бы интересно глянуть.
все настройки берутся по умолчанию (в том числе innodb_buffer_pool_size = 8 Мб и прочее)
категорически согласен: смысла использовать SQL и при том не использовать индексы — ноль.

и вместо чтения исследований производительности без использования индексов (которое возможно и имеет какой-то сугубо теоретический смысл), новичкам лучше почитать про использование этих самых индексов. этот способ оптимизации гораздо лучшие, чем конвертирование табличек MyISAM <=> InnoDB.
Ух, сколько людей не поняли сути происходящего, судя по плюсам. Объясняю.
Запрос выглядит так: FROM calls JOIN users ON calls.user_id = users.id Т.е. Join делается с индексом — первичным ключем таблицы users.

Индексы намеренно не используются для выборки значений из таблицы calls. Это сделано для того, чтобы померить только скорость джойна, а не выборки нужных строк из calls. Автор замерил скорость full scan для этой таблицы, и затем замерял скорость того же full scan + join для какого-то процента записей. Т.е. время чистого джойна можно найти вычитанием одного времени из другого.

Если бы для поля, по которому делается выборка в таблице calls, использовались бы индексы, то на маленьком проценте строк мы бы получили быстрый выбор строк и быстрый join, но все равно почти все время можно было би считать временем join. А вот на большом проценте строк все было бы не так очевидно, ведь с определенного момента mysql перестал бы использовать индексы для поиска строк таблицы calls, и мы бы вернулись к full scan, т.е. время запроса было бы уже суммой full scan + join. При этом на в промежутках было бы непонятно, сколько собственно времени занимает join, а сколько выборка строк.
возможно вы и правы, но все же неплохо было бы увидеть цифорки.
4 8 15 16 23 42

В топике полно цифр, что бы вы еще хотели увидеть?
Время выполнения с индексом по calls.user_id.

Хотя подозреваю, что этот индекс ничего не даст. Но всё же.
Этот индекс действительно ничего не даст, это поле не используется для выбора записей. Вот индекс по calls.cost мог бы помочь в первом случае, для 0.1% строк.
спасибо за пояснение. лично я, при таком сравнении циферок, как приводится в статье, этого сразу не понял (тупой, да).

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

я попытался воспроизвести тест и попробовал добавить индекс на calls.user_id. и от него стало только хуже (глядя на результаты explain становится понятно почему).

однако составной индекс на столбцы user_id + cost решает проблему для данного запроса и он начинает выполняться существенно быстрее (тоже понятно почему). у меня время выполнения запроса с 100% для MyISAM уменьшилось в 40 раз (с ~130 сек до ~3 сек).

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

P.S. ничего против InnoDB я не имею. сказанное мной выше относится и к ним.
Нет, нет, нет, все не так, все не о том.

Смотрите, как называется топик: «Исследуем производительность JOIN в MySQL». Не строим оптимальные запросы, ни учимся ставить индексы, исследуем производительность JOIN. Что делает автор? Он берет огромную таблицу и заставляет mysql пройтись по ней всей. Это называется full scan. Потом, он в эту же таблицу подмешивает join по первичному ключу из другой таблицы. Время выборки дофига строк нам известно, теперь нам известно время подключения другой таблицы.

Берем для разного количества строк вычитаем время на full scan, откладываем по оси y, а по оси x — количество строк, для которых был сделан join. Получаем:

twitpic.com/5e6und

Видим, что график линейный, а значит скорость join для одной строки не зависит от количества строк и mysql (я взял данные для innodb) способен делать join для 800к строк в секунду.

Не знаю, как вам, а для меня это полезная информация.
OK, OK, я влез в дискуссию не о том.

P.S. когда обсуждается, «микроскопом А или микроскопом Б лучше забивать гвозди?», то с бредовым предложением забыть про микроскоп (тип таблиц) и воспользоваться молотком (индексами) в дискуссию лучше не влезать, ибо она тут не к месту.

Слушайте, ну это уже не смешно. Для JOIN используются индексы, я честно вам говорю.
я вам верю.
мы просто говорим про разные вещи.
предлагаю завязать.
Вы говорите, что вместо того, чтобы мерить скорость джойнов для разных типов таблиц нужно использовать индексы. Но они уже используются. Почему о разных то?
Я рассмотрю любые предложения, какие индексы поставить для данного запроса с FULL SCAN к Calls, с учетом того, что доп. поля в таблицу calls добавлять нельзя.
У вас такой запрос, что хоть заиндексируйся — прироста не будет. Из users вы дергаете примерно половину записей (предполагаю, что мужчин и женщин там поровну), а потом все это соединяете со своими 10 миллионами записей в calls.
Имхо, от такого исследования толку нет — в рабочей системе таким запросам явно не место. И начинающим программистам (для которых, как я понимаю, вы это все и делали) лучше дать совет использовать соединения там где они нужны, используя при этом правильные индексы и думать над тем, что реально выполняет их запрос.
ну почему-же… чуть лучше станет если добавить, например, вот такой составной индекс:
create index ix_calls_0 on calls (cost, user_id);

а ещё у меня чуть быстрее получилось, если запрос переписать так:
SELECT COUNT(cost) FROM calls INNER JOIN (SELECT id FROM users WHERE sex = 'M') a ON (calls.user_id = a.id) WHERE calls.cost > %d;

последнее ускорило и join MyISAM+MyISAM и InnoDB+InnoDB (извращения типа MyISAM+InnoDB я не проверял)
не совсем верно выразился: индексы на users к увеличению производительности не приведут. Просто я его писал в контексте того комментария, на который давал ответ. А там было условие оставлять FULL SCAN на calls.
Поставил плюс за оригинальный INNER JOIN
> И начинающим программистам лучше дать совет использовать
> соединения там где они нужны
Дайте! С своей статье, например. Эта же статья преследует другие цели и весьма полезна.
MyISAM + InnoDB, который работает в 2 раза быстрее, чем MyISAM + MyISAM — странный способ оптимизации. Ведь связей много разных в т.ч. часто приходится делать и обратную связь ( `users` join `calls` ) — в результате получится InnoDB + MyISAM.

Кроме того, из статью очевидно, что при джоинах используются какие-то внутренние алгоритмы позволяющие максимально использовать внутреннюю архитектуру движков. В таком случае, добавление индексов может полностью всё изменить.
Я честно признаюсь — поставил статье минус.

Тут просто ну совсем не в кассу — InnoDB и MyISAM настолько разные, что на дефолтных настройках тут даже говорить нечего. InnoDB на 99% зависит от innodb_buffer_pool. Это его пулл данных, а данные он любит иметь в памяти, поэтому все кто работают хоть сколько-то с InnoDB — всегда подстраивают. Потому что это главная фишка самого InnoDB. А когда у вас 10 млн. записей и вы даёте буффер в 2% от объёма базы — конечно вы будете жуть как упираться в джоины, подгрузку индексов и.т.д. В отличии от MyISAM — у InnoDB индексы и данные идут в innodb_buffer_pool — у вас даже primary key индекс в память не влазит — приходится с диска сканировать. А у MyISAM всё это дело настраивается отдельно и имеет свои отдельные буфферы и в таком варианте конечно работает быстрее.
В общем как по мне — обсалютно мимо.
Для таблицы Calls не влазит, а вот для Users — влазит целиком. Поэтому, ИМХО, действительно стоит вычеркнуть результаты InnoDB + InnoDB (что, в общем-то, итак видно по получившимся цифрам)
В любом случае данные приходится постоянно читать с диска мизерными пачками и все оптимизации InnoDB улетают в трубу. А поскольку это транзакционный движок (в отличии от MyISAM), то и накладных расходов в этом случае у него много.
join без индекса — это ужас )

напомню, что вариантов join просто немалое количество (full scan, hash, merge, как минимум).
Join с индексами, без индексов просмотр таблицы calls, все правильно.
В силу разности архитектуры этих поисковых движков интереснее было бы сравнивать не MyISAM и InnoDB, а каждый из них с со своими пропатченными версиями. Я имею ввиду InnoDB vs XtraDB и Aria против MyISAM. Имхо это было бы более объективно и полезно.
> запросы прогонялись несколько раз, чтобы убедиться, что всё попадает в кеш.

Если хочется тестировать скорость JOIN (пусть и сферическую, без индексов, в вакууме), то правильнее было-бы отключить кэширование (опциями в запросе), чтобы тестировать именно скорость JOIN, а не то, какой именно запрос кэшируется и как быстро работает кэш в разных случаях.
Кеширование запросов, очевидно, отключено, иначе были бы совсем другие цифры.
UFO just landed and posted this here
тем более что, скорее всего результаты:
двух MyISAM скорее всего не влезли в буфер,
как и результаты двух InnoDB.
а их пересечение использовало два буфера и они влезли. хотя это все еще надо тестировать…
UFO just landed and posted this here
1. Использование InnoDB на дефолтных настройках.
2. Во-вторых, использование JOIN без индексов.
3. При включенном кэше была измерена скорость именно кэша, а не JOIN.
4. Во всех ваших таблицах MyISAM рвет InnoDB, а в комментах вы пишете обратное.
5. Использование

В общем вердикт такой: вы сделали неправильно все, что только можно было сделать при тестировании. А потом еще умудрились перепутать результаты.
5. Использование далеко не лучшего инструмента для замеров.
2. JOIN с индексами!
3. Кеш выключен (согласен, автору нужно было указать)
4. MyISAM рвет в FULL SCAN. Вычитаем из обоих время FULL SCAN, получаем что в InnoDB JOIN действительно быстрее (согласен, автору нужно было сразу написать и абсолютные цифры и за вычетом FULL SCAN)
5. Это действительно важно, когда счет идет на секунды, а не на микросекунды?
JOIN С ИНДЕКСАМИ! Люди, где ваши глаза?
В первой таблице идет MyISAM потом InnoDB, во всех остальных — наоборот, что сбивает
Спасибо за замечание, поправил :)
Товарищи, которые считают, что innodb_buffer_pool_size влияет на скорость FULL SCAN, поставьте пожалуйста плюс этому комментарию. Таблица Users в пул InnoDB, при этом, влезала целиком.

Если найдется много «верующих» в то, что результаты FULL SCAN зависят от этой опции — я сделаю отдельный тест. Пожалуйста всем, кто считает, что что-то изменится, пожалуйста, приложите предлагаемые опции для InnoDB.

Кто знает, как заставить MyISAM делать более быстрый JOIN — тоже буду очень рад услышать.

P.S. Размер таблицы Calls в InnoDB — около 500 Мб, в MyISAM — примерно столько же (с учетом размера индексов). Users — 5 Мб
UFO just landed and posted this here
Ээээ…
1) Какие индексы вы предлагаете поставить???
2) кеш запросов MySQL выключен, а про какие ещё кеши вы говорите, я не очень понимаю
Можно же поставить дополнительные индексы — и ощутить разницу.
Насчёт траффика тут двоякая ситуация. Когда таблицы джойнятся с отношениями многих к одному, траффика в обратную сторону больше выйдет.
1. Хотелось бы видеть планы всех запросов.
2. Сколько мегабайт суммарно весят все ваши данны (и сколько индексы?) Хотелось бы видеть результаты для больших объемов. Для случаев, когда все данные в память запихать не удается, и джойт делается на диске.
Весьма печально видеть, как пост о тестировании производительности чего-то, каких-то запросов в СУБД, в котором НИ СЛОВА не сказано о планах запросов, автор измеряет только время, измеряет его на своем железе, конфиг которого мы не знаем, набирает столько плюсов от некомпетентных читателей.
Я уж не спрашиваю, а не запускал ли автор часом свои запросы один после другого, когда данные все лежат в кеше (например, кеше файловой системы, ибо я ОЧЕНЬ сомневаюсь, что у автора на ноуте стоит raw storage device).
План запросов не очевиден? Конкретные характеристики играют роль, при сравнении на одной машине?

Расскажите, на примере, что мы могли бы теоретически узнать из железа и плана запросов такого, что в корне изменило бы выводы статьи?
А вы когда-нибудь слышали такое понятие «инжерерный подход к измерению и повышению производительности»? Видите ли, я навидался кода людей, которым «очевиден план запроса». И людей, которые полагают что знают, каким он должен быть (правда, это было на оракле).

Конкретные характеристики — например, размер оперативки, размер кеша процессора, сколько памяти выделено под мускул? Таблица все загружается в память? Индексы все загружаются в память? Cache hit ratio какой? Общее потребление памяти в процессе джойна? IO — узкое место любой нагруженной БД, если только она не игрушечного размера и не держится вся в памяти. Что про IO? Какой жесткий диск? Какая нагрузка на него идет в процессе джойна?

Если копнуть, то я могу много чего сказать. Например вот — «Результаты (погрешность менее 5%):». Это откуда? «Я гарантирую это!»?

Вы поймите. Теоритически я мог бы узнать что-то специфичное для машины автора, для его конкретного приложения. Но из того, что изложено здесь — я не вижу никакого права у автора обобщать результаты своего эксперименты и давать более широкие советы. Советы для реальных приложений. Типа такого:

если нужно много джойнить, и почему-то нужна высокая производительность, используйте комбинацию MyISAM + InnoDB, или просто InnoDB + InnoDB, если джойнов больше одного. Ну а если вы — экстремал, можете использовать MyISAM + MEMORY для получения максимальной производительности при любых сценариях. Есть ещё вариант с MEMORY+MEMORY, но для большого количества записей я бы не стал так делать ;).
> Таблица все загружается в память?
Судя по её размеру — да.

> Cache hit ratio какой?
Кеш отключен, 10 раз сказано.

> Общее потребление памяти в процессе джойна?
По-моему запрос специально выбран такой, чтобы память для результатов вообще не требовалась. Это видно же.

Итого: во всех тестах измерялась скорость работы с данными, а не скорость подгрузки их с диска, что и являлось первоначальной задачей. Разве нет?
Кеш чего? Буферный кеш таблиц? Кеш индексов? Кэш распарсенных планов? Query result cache?
А зачем что-то кроме Query result cache отключать для теста?
Видите ли, если вы выполните один и тот же запрос с отключенным резалт кешем, но включанным буферным кешем (так это называется в оракле, как в мускуле не помню), два раза после старта сервера, то второй будет внезапно, гораздо быстрее. Внимание! Почему?
Ну вы уж вспомните, пожалуйста, или где-нибудь посмотрите, а то мы вообще не узнаем, о чем речь.
Кеш, в котором лежат блоки данных, из таблиц, индексов, и прочих объектов. Не result cache.
В общем, ваша надменность немного огорчает. Если вы знаете Oracle, то пожалуйста, перед тем, как лезть в темы про MySQL, постарайтесь ознакомиться с матчастью. В MySQL нет такого кеша — см. мой комментарий ниже.
В MyISAM, например, нет кеша для данных, а для InnoDB есть, а для MEMORY это понятие вообще не имеет смысла. Запросы прогонялись несколько раз, чтобы всё попало в мифический «буферный кэш в MySQL», и времена брались для запросов, которые уже прокешировались.
Таблица все загружается в память?
Судя по её размеру — да.

А вот чтоб не СУДИТЬ, а ЗНАТЬ, я и спросил характеристики.

По-моему запрос специально выбран такой, чтобы память для результатов вообще не требовалась. Это видно же. Итого: во всех тестах измерялась скорость работы с данными, а не скорость подгрузки их с диска, что и являлось первоначальной задачей. Разве нет?

Нда. Память нужна для самой операции джойна, ну да ладно.

Если измеряется скорость работы с данными, без учета скорости диска, тогда зачем эта загрузка с диска происходит вообще, зачем отключен кеш?
Именно IO — часто узкое место. У вас может быть 80% времени это чтение блоков данных из датафайла, и 10% времени работа с данными в памяти. Вы это хотите мерять?
> Память нужна для самой операции джойна
По первой таблице fullscan, результат не накапливается, сохраняется только одна переменная — максимальное число. Поиск строки для присоединения происходит по ключу для каждой (или не каждой) строки. Что я упускаю?

> тогда зачем эта загрузка с диска происходит вообще
Какая загрузка?

> зачем отключен кеш?
Чтобы на 2-й такой-же запрос база не ответила за 0,2 мс, и можно было посчитать среднее время выполнения запроса. Уж такой-то профи должен знать.

> Именно IO — часто узкое место.
Спасибо, капитан.
Так.

1) EXPLAIN я могу показать, но из него вы мало чего увидите. Именно по причине, что EXPLAIN этих SELECT'ов не давал никакой полезной информации, я и не стал их приводить. Мускул это вам не оракл. Какой план был выбран для выполнения запроса, можно легко увидеть по временам исполнения запросов.
2) Зачем вам конфиг моего железа? Я написал, что это мой ноутбук с Mac OS X. Памяти (ОЗУ) достаточно, чтобы всё влезало в дисковый кеш. Результаты прогонялись несколько раз, загрузка CPU всё время была >=100% на всё время выполнения запроса.
3) Конфиг мускуля выбран дефолтный: я согласен, я мог бы и потюнить базу, и в следующий раз так и сделаю, чтобы результаты для InnoDB (и, возможно, MyISAM) были более объективные. Я проводил тесты и на базе, затюненной под MyISAM, и результаты для MyISAM и MEMORY получились примерно такие же (InnoDB я не тестировал).

Далее.

Я не делал тестов по Oracle, ибо я не разбираюсь в том, как работает Oracle. Но зато я разбираюсь в том, как работает MySQL, по моему мнению, достаточно хорошо, чтобы давать такие тесты, которые дают ПРИМЕРНУЮ оценку производительности различных типов JOIN.

«Погрешность менее 5%» основана на воспроизводимости результатов. Запросы прогонялись несколько раз. Результат первого запроса игнорировался (чтобы всё попало в кеш (не Query Cache, который отключен) MySQL и ФС), и бралось среднее значение от нескольких следующих запросов. Времена укладывались в 5%.

Ещё раз повторяю про IO — таблица и индексы попадали целиком в кэш ФС. Т.е. это означает, что ВСЁ В ПАМЯТИ, НИКАКОГО I/O.
Понятно. youROCK, homm — спасибо за ваши комментарии и пояснения. Дальше читатали пусть делают из комментариев выводы сами. Не хочу выглядеть надменным.
UFO just landed and posted this here
Эти условия при тестировании были итак соблюдены.
а как вы заполняли таблицу? просто рандомом?
innoDB же может использовать локальность данных.
к тому же у innoDB быстрее в других, не связанных с полным сканированием таблицы (работа с диапазоном значений, к примеру, что гораздо чаще используется).
к тому же, наличие транзакций по-моему перекрывает всё остальное в любом случае.
Да, рандомом. Ну и также понятно, что у InnoDB есть куча своих сильных сторон — тот же быстрый JOIN. Но я не ставил целью вообще сравнивать движки хранения данных, а исследовал скорость JOIN, чтобы каждый мог сам для себя решить, нужно им использовать эту функциональность СУБД :)
а как вы заполняли таблицу? просто рандомом?
innoDB же может использовать локальность данных.
к тому же у innoDB быстрее в других, не связанных с полным сканированием таблицы (работа с диапазоном значений, к примеру, что гораздо чаще используется).
к тому же, наличие транзакций по-моему перекрывает всё остальное в любом случае.
пардон, что-то у меня подвисло. камент выше
Sign up to leave a comment.

Articles