четверг, 21 апреля 2011 г.

Дубликация данных при выполнении JOIN-запросов

Давайте представим, что у нас есть две таблицы: Users (UserId, UserName, UserPhoto) и Articles (ArticleId, UserId, ArticleText). А теперь сделаем выборку пользователей со статьями при помощи Join:

SELECT UserId, UserName, UserPhoto, ArticleId, ArticleText
FROM Users as u INNER JOIN Articles as a ON u.UserId = a.UserId

Структура ответа на этот запрос будет следующей:

UserId1 UserName1 UserPhoto1 ArticleId1 ArticleText1
UserId1 UserName1 UserPhoto1 ArticleId2 ArticleText2


Обратите внимание на дублирующиеся UserName1 и UserPhoto1. А ведь в одном из этих полей может храниться большой blob. А теперь представьте, что будет при большем количестве JOIN'ов!

Меня удивляет, что упоминания этого нюанса нет в каждой серьезной статье про JOIN большими красными буквами. Более того, я ни разу не встречал такого упоминания. Скажете, что это очевидно? Как сказать... Это неочевидно как минимум при использовании ORM (особенно для тех программистов, кто не работал с голым SQL).

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

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


  1. Выполнение нескольких запросов в стиле lazy loading. Сначала считываем людей, а потом считываем статьи для пользователей со считанными идентификаторами. Мы избавляемся от JOIN и дубликации, но порождаем лишний запрос.

  2. Выполнение первого пункта в рамках хранимой процедуры с возвращением нескольких ResultSet'ов. Как и в первом варианте будет выполнено два запроса, но экономим один round trip.

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

  4. Реализовать запрос, который агрегирует выборку для каждого пользователя в один кортеж (например, в формате CSV). Преимущества и недостатки аналогичны пункту 3.


Если вдруг дубликация при JOIN-выборках станет узким местом в производительности вашего приложения, теперь вы знаете, что делать :)

Комментариев нет: