Почему объединения плохи с точки зрения масштабируемости?


94

Почему присоединяется плохо или «медленно». Я знаю, что слышал это не раз. Я нашел эту цитату

Проблема в том, что соединения относительно медленные, особенно для очень больших наборов данных, и если они медленные, ваш сайт будет медленным. Чтобы снять с диска все эти отдельные фрагменты информации и снова собрать их вместе, требуется много времени.

источник

Я всегда думал, что они быстрые, особенно когда смотрел на ПК. Почему они «медленные»?

sql  join 

Ответы:


100

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

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

Но помните, что альтернатива больше не имеет двух отдельных частей данных; вы должны поместить две разные точки данных в одну запись. Вы не можете объединить два разных фрагмента данных без каких-либо последствий, поэтому убедитесь, что вы понимаете компромисс.

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

  • Присоединитесь по суррогатному ключу (столбец autonumer / identity), а не по естественному ключу. Это означает меньшие (и, следовательно, более быстрые) сравнения во время операции соединения.
  • Индексы
  • Материализованные / индексированные представления (воспринимайте это как предварительно вычисленное соединение или управляемую денормализацию)
  • Вычисляемые столбцы. Вы можете использовать это для хеширования или иным образом предварительно вычислить ключевые столбцы соединения, так что то, что было бы сложным сравнением для соединения, теперь намного меньше и потенциально предварительно индексируется.
  • Разделы таблицы (помогает с большими наборами данных, распределяя нагрузку на несколько дисков или ограничивая то, что могло быть сканированием таблицы, вплоть до сканирования раздела)
  • OLAP (предварительно вычисляет результаты определенных типов запросов / объединений. Это не совсем так, но вы можете думать об этом как об общей денормализации)
  • Репликация, группы доступности, доставка журналов или другие механизмы, позволяющие нескольким серверам отвечать на запросы чтения для одной и той же базы данных и, таким образом, масштабировать вашу рабочую нагрузку между несколькими серверами.
  • Использование уровня кэширования, такого как Redis, чтобы избежать повторного выполнения запросов, требующих сложных соединений.

Я бы сказал, что основная причина, по которой реляционные базы данных вообще существуют, - это возможность эффективно выполнять соединения * . Это, конечно, не только для хранения структурированных данных (вы можете сделать это с помощью таких конструкций плоских файлов, как csv или xml). Некоторые из перечисленных мною опций даже позволят вам заранее полностью построить ваше соединение, поэтому результаты уже получены до того, как вы отправите запрос - так же, как если бы вы денормализовали данные (по общему признанию, за счет более медленных операций записи).

Если у вас медленное соединение, вы, вероятно, неправильно используете свою базу данных.

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

* То есть существуют как объекты, отличные от простых наборов таблиц. Дополнительная причина для настоящего rdbms - безопасный одновременный доступ.


14
Индексы, вероятно, должны быть вверху списка. Многие ( кашляющие ) разработчики, кажется, забывают о них, когда тестируют небольшой набор данных, а затем ставят базу данных на колени в производственной среде. Я видел запросы, которые выполнялись в 100 000 раз быстрее, просто добавляя индексы. И это произвольные индексы без глубокого анализа данных для определения наилучшего сочетания для соответствия крайнему левому префиксу.
Дункан,

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

В третьем пункте вы упоминаете «Материализованные / проиндексированные представления». Вы говорите о обычных представлениях SQL или о чем-то другом?
slolife

@slolife обычные представления sql похожи на выполнение дополнительного запроса в фоновом режиме на лету, когда вы используете запрос, который ссылается на представление. Но вы также можете указать серверу sql «материализовать» некоторые представления. Когда вы это сделаете, sql-сервер будет хранить дополнительную копию данных представления, как и обычная таблица, так что, когда вы ссылаетесь на представление в запросе, ему больше не нужно запускать этот запрос в фоновом режиме, потому что данные уже есть. . Вы также можете поместить в представление индексы, отличные от индексов исходной таблицы, чтобы еще больше помочь вам настроить производительность.
Джоэл Коухорн

Спасибо, Джоэл. Я должен разобраться в этом.
slolife

29

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

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


2
... за исключением MySQL, который, похоже, имеет проблемы с производительностью при большом количестве соединений независимо от того, как выглядят ваши индексы. Или, по крайней мере, было в прошлом.
Powerlord

2
Принято считать, что если есть известные проблемы с конкретной СУБД (и, возможно, даже с версией), тогда этот совет может иметь смысл, но как общий совет он довольно вводит в заблуждение, если вы используете реляционную базу данных. При этом нереляционные механизмы хранения становятся все более популярными. Примеры - SimpleDB от Amazon и CouchDB ( couchdb.apache.org ). Если вам лучше отказаться от реляционной модели, вам, вероятно, следует оставить продукты, которые оптимизированы, и поискать другие инструменты.
Тендаи Мавуше,

13

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


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

12

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

В академическом мире мы узнаем о таких вещах, как различные нормальные формы (1-я, 2-я, 3-я, Бойса-Кодда и т. Д.), И мы узнаем о различных типах ключей (первичный, внешний, альтернативный, уникальный и т. Д.) И о том, как эти вещи подходят друг другу для создания базы данных. И мы изучаем основы SQL, а также манипулируем структурой и данными (DDL и DML).

В корпоративном мире многие академические конструкции оказываются существенно менее жизнеспособными, чем нас заставляли думать. Прекрасный пример - понятие первичного ключа. С академической точки зрения это тот атрибут (или набор атрибутов), который однозначно определяет одну строку в таблице. Таким образом, во многих проблемных областях правильный академический первичный ключ состоит из 3 или 4 атрибутов. Однако почти все в современном корпоративном мире используют автоматически сгенерированное последовательное целое число в качестве первичного ключа таблицы. Зачем? Две причины. Во-первых, это делает модель намного чище, когда вы переносите FK повсюду. Второй и наиболее уместный для этого вопроса заключается в том, что получение данных с помощью объединений выполняется быстрее и эффективнее для одного целого числа, чем для 4 столбцов varchar (как уже упоминалось некоторыми людьми).

Давайте теперь углубимся в два конкретных подтипа реальных баз данных. Первый тип - это транзакционная база данных. Это основа для многих приложений электронной коммерции или управления контентом, управляющих современными сайтами. С транзакционной БД вы сильно оптимизируете «пропускную способность транзакций». Большинство приложений для торговли или контента должны сбалансировать производительность запросов (из определенных таблиц) с производительностью вставки (в других таблицах), хотя каждое приложение будет решать свои собственные уникальные бизнес-задачи.

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

В каждом случае разработчик или администратор баз данных должен тщательно сбалансировать кривые функциональности и производительности, и есть множество приемов повышения производительности с обеих сторон уравнения. В Oracle вы можете делать то, что называется «планом объяснения», чтобы вы могли конкретно видеть, как запрос анализируется и выполняется. Вы хотите максимально использовать индексы в БД. Одно действительно неприятное запрещение - это поместить функцию в предложение where запроса. Каждый раз, когда вы это делаете, вы гарантируете, что Oracle не будет использовать какие-либо индексы в этом конкретном столбце, и вы, вероятно, увидите полное или частичное сканирование таблицы в плане объяснения. Это всего лишь один конкретный пример того, как можно написать запрос, который в конечном итоге оказывается медленным и не имеет ничего общего с соединениями.

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

Поговорим о нормализации минутку. Это еще одна в значительной степени положительная академическая тема, которую можно переоценить. В большинстве случаев, когда мы говорим о нормализации, мы действительно имеем в виду устранение повторяющихся данных путем помещения их в отдельную таблицу и переноса FK. Люди обычно пропускают всю зависимость, описываемую 2NF и 3NF. И все же в крайнем случае, безусловно, возможно иметь совершенную базу данных BCNF, которая огромна и полная зверюга для написания кода, потому что она настолько нормализована.

Итак, где нам балансировать? Нет лучшего ответа. Все лучшие ответы, как правило, представляют собой некоторый компромисс между простотой обслуживания структуры, простотой обслуживания данных и простотой создания / обслуживания кода. В общем, чем меньше дублирование данных, тем лучше.

Так почему же соединения иногда бывают медленными? Иногда это плохой реляционный дизайн. Иногда это неэффективная индексация. Иногда это проблема с объемом данных. Иногда это ужасно написанный запрос.

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


10

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

Есть много причин не деномализировать. Во-первых, скорость выборочных запросов - не единственная и даже не главная проблема баз данных. Первоочередная задача - целостность данных. Если вы денормализуете, тогда вам нужно применить методы, чтобы данные оставались денормализованными по мере изменения родительских данных. Итак, предположим, что вы собираетесь хранить имя клиента во всех таблицах вместо присоединения к клиентской таблице по client_Id. Теперь, когда имя клиента изменяется (100% вероятность, что некоторые имена клиентов со временем изменятся), теперь вам нужно обновить все дочерние записи, чтобы отразить это изменение. Если вы сделаете это с каскадным обновлением и у вас будет миллион дочерних записей, как вы думаете, насколько быстро это будет происходить и сколько пользователей будут страдать от проблем с блокировкой и задержек в своей работе, пока это произойдет? Кроме того, большинство людей, которые денормализируют, потому что "

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

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

Если вам просто нужна возможность запросов, то да, вы можете создать хранилище данных, которое можно денормализовать и заполнить с помощью инструмента ETL (оптимизированного для скорости), а не ввода данных пользователя.


8

Присоединение происходит медленно, если

  • данные неправильно проиндексированы
  • результаты плохо отфильтрованы
  • плохо написанный запрос присоединения
  • наборы данных очень большие и сложные

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

Ваш источник дает возможность денормализации. Это нормально, только если вы исчерпали лучшие альтернативы.


7

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

Как это:

SELECT  SUM(transaction)
FROM    customers
JOIN    accounts
ON      account_customer = customer_id

Даже если индекс задан account_customer, все записи из последнего все равно нужно просканировать.

Для этого списка запросов достойные оптимизаторы, вероятно, даже не будут рассматривать путь доступа к индексу, вместо этого выполняя a HASH JOINили a MERGE JOIN.

Обратите внимание, что для такого запроса:

SELECT  SUM(transaction)
FROM    customers
JOIN    accounts
ON      account_customer = customer_id
WHERE   customer_last_name = 'Stellphlug'

соединение, скорее всего, будет быстрым: сначала customer_last_nameбудет использоваться индекс on для фильтрации всех Stellphlug (которых, конечно, не очень много), затем будет выполнено сканирование индекса account_customerдля каждого Stellphlug, чтобы найти его транзакции.

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


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

1
Если индекс определен в accounts(account_customer)большинстве СУБД, он будет использовать этот индекс, чтобы точно определить, какие строки customersбазы данных необходимо сканировать.
jemfinch

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

@jemfinch: нет, не будут. Для этого потребуется сканирование всего индекса только для фильтрации клиентов, а затем сканирование индекса клиентов во вложенном цикле. A HASH JOINбудет намного быстрее, поэтому он будет использоваться, за исключением всех основных баз данных, за исключением того MySQL, что будет просто делать customersведущие во вложенном цикле (поскольку он меньше по размеру)
Quassnoi

4

Joins are fast.Соединения следует рассматривать как стандартную практику с правильно нормализованной схемой базы данных. Объединения позволяют объединять разрозненные группы данных осмысленным образом. Не бойтесь соединения.

Предостережение: вы должны понимать нормализацию, объединение и правильное использование индексов.

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

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

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


3

Соединения действительно требуют дополнительной обработки, поскольку им нужно искать в большем количестве файлов и индексов, чтобы «соединить» данные вместе. Однако «очень большие наборы данных» относительны. Какое определение для большого? В случае JOINs, я думаю, что это ссылка на большой набор результатов, а не на общий набор данных.

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

Как только ваш набор результатов начнет расти, все замедлится. Используя тот же пример, если основная таблица дает 100 000 записей, то необходимо найти 500 000 «объединенных» записей. Просто вытащить столько данных из базы данных с дополнительными задержками.

Не избегайте JOIN, просто знайте, что вам может потребоваться оптимизация / денормализация, когда наборы данных становятся «очень большими».


3

Также из цитированной вами статьи:

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

а также

И если вы не действительно большой веб-сайт, вам, вероятно, не стоит беспокоиться об этом уровне сложности.

а также

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

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


2

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


Я не уверен, что это правда. Я знаю, что Teradata, безусловно, может распределять соединения между усилителями. Очевидно, что некоторые типы соединений могут быть сложнее / сложнее, чем другие.
Cade Roux,

индексы могут быть разделены в СУБД от mysql до oracle. AFAIK, который масштабируется (распределяется и может работать параллельно).
Unreason

2

Правильно оформленные таблицы, содержащие правильные признаки и правильно написанные запросы, не всегда работают медленно. Где бы вы это ни слышали:

Почему присоединения плохие или медленные

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


1

Количество генерируемых временных данных может быть огромным из-за объединений.

Например, в одной из баз данных здесь, на работе, была общая функция поиска, в которой все поля были необязательными. Процедура поиска выполняла объединение всех таблиц перед началом поиска. Вначале это сработало хорошо. Но теперь, когда в основной таблице более 10 миллионов строк ... не так много. Теперь поиск занимает 30 минут или больше.

Мне было поручено оптимизировать хранимую процедуру поиска.

Первое, что я сделал, - если в каком-либо из полей основной таблицы производился поиск, я выбрал временную таблицу только для этих полей. ЗАТЕМ, я объединил все таблицы с этой временной таблицей перед тем, как продолжить поиск. Поиск по одному из основных полей таблицы теперь занимает менее 10 секунд.

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

Загрузка ЦП сервера SQL также СНИЗИЛАСЬ.


@BoltBait: Это сообщение о том, что вы всегда должны пытаться уменьшить количество строк, прежде чем выполнять соединение?
unutbu

В моем случае это определенно творило чудеса. Но я бы не стал оптимизировать систему, пока в ней не возникнет необходимость.
BoltBait

обычно временные данные не генерируются при соединениях (в зависимости, конечно, от избирательности, доступной памяти и размера буферов соединения), AFAIK; однако временные данные обычно создаются по порядку и отдельно, если нет индекса, который можно использовать для таких операций.
Unreason

1

В то время как соединения (предположительно из-за нормализованного дизайна), очевидно, могут быть медленнее для извлечения данных, чем чтение из одной таблицы, денормализованная база данных может быть медленной для операций создания / обновления данных, поскольку объем всей транзакции не будет минимальным.

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


1

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

Примеры, приведенные в статье - Flickr и eBay - являются исключительными случаями, ИМО, поэтому имеют (и заслуживают) исключительные ответы. Автор особо отмечает отсутствие RI и степень дублирования данных в статье.

Большинство приложений - опять же, IMO - выигрывают от проверки и уменьшения дублирования, обеспечиваемых СУБД.


0

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.