Оптимизация запроса - используя поле или другую таблицу - PullRequest
2 голосов
/ 28 февраля 2011

У меня есть запрос, который занимает много времени, и я хочу его оптимизировать. Я ищу самый эффективный способ сделать это.

Я работаю над Hibernate / JPA с Postgresql DB, но любое решение должно быть общим JPA.

Терминология

  • Пользователь: пользователь в системе.
  • Друг: друг пользователя. У пользователя будет N друзей.
  • Сеанс: сеанс использования системы. Может быть открытым или закрытым.
  • Контекст: контекст сеанса. Пользователь может иметь один открытый сеанс на контекст в любой момент времени и может иметь много прошлых закрытых сеансов на контекст.

Запрос

Мне нужно реализовать запрос, который, учитывая имя пользователя, дает мне следующее:

  • Получить всех друзей этого пользователя
  • Для каждого друга:
    • Если у друга есть открытые сеансы, извлеките все открытые сеансы (для всех контекстов)
    • В противном случае выведите последнюю сессию друга из всех контекстов.

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

* Пример 1 040 *

У пользователя A есть три друга: B, C, D. Есть два контекста, 1 и 2. У друзей есть следующие данные:

(форматирование ниже: идентификатор сеанса - пользователь, контекст)

  • 1 - B, 1: открытый сеанс
  • 2 - B, 2: Закрытая сессия, которая началась 27 февраля
  • 3 - B, 2: закрытый сеанс, который начался 26 февраля
  • 4 - C, 1: закрытый сеанс, который начался 27 февраля
  • 5 - C, 1: закрытый сеанс, который начался 26 февраля
  • 6 - C, 2: закрытое заседание, которое началось 26 февраля
  • 7 - C, 2: Закрытая сессия, которая началась 25 февраля
  • 8 - D, 1: открытая сессия
  • 9 - D, 2: открытая сессия

Запрос должен получить меня: B: Сессия 1 (Все открытые сессии) C: Сессия 4 (Последнее закрытое заседание) D: Сессии 8,9 (Все открытые сессии)

Текущее состояние

Мой запрос работает в три этапа:

  1. Получить всех друзей пользователя
  2. Для каждого друга:
    1. Получить все открытые сессии для друга
    2. Если есть открытый сеанс, вернуть все открытые сеансы
    3. Получить последнюю сессию для друга, вернуть эту сессию

Очевидно, это много запросов. Для начала, я собираюсь сделать шаг 2 выше и преобразовать его в один запрос . Мои опасения связаны с этим вторым запросом. Вопрос в том, как сделать его более оптимизированным. Поэтому проблему можно перефразировать:

"Учитывая набор из N идентификаторов друзей, получите все открытые сессии или самую последнюю сессию для всех этих друзей."

Предлагаемые решения

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

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

  • Создание новой сущности и таблицы для "последних сеансов"
  • Таблица будет иметь следующие столбцы:
    • Пользователь
    • Контекст
    • ID последней сессии
  • Таблица будет обновляться объектом сеанса после сохранения, так что любой вновь сохраняемый сеанс будет автоматически обновлять эту таблицу.
  • Новый запрос будет извлекать все записи для всех друзей пользователя из этой таблицы и работать над ними, чтобы создать конечный результат.

В решении для столбцов указано, что в таблице сеансов должен быть столбец «последнего» флага. Последствия этого решения:

  • Создать новое поле для последней (логическое значение)
  • Столбец будет установлен после записи persist объекта сеанса, так что предыдущий "последний" сеанс больше не будет самым последним, а новый сеанс станет самым последним.
  • Новыйquery извлечет все последние записи (включив новый столбец в условие оператора) для всех друзей пользователя из исходной таблицы сеансов и поработает над ними, чтобы создать конечный результат.

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

Ответы [ 3 ]

1 голос
/ 28 февраля 2011

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

Однако учтите, что «вы делаете это неправильно» (согласно теории).

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

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

Кроме того, если вы действительно хотите пойти по этому пути (отключив СУБД для поиска оптимального плана выполнения), то наиболее важными факторами являются:

  • индексы (повлияет на производительность на порядки)
  • шаблоны использования (индексы улучшат производительность SELECT, но слишком большое количество индексов замедлит обновления)
  • Кэширование на уровне приложения / клиента (может влиять на производительность и масштабируемость на порядки)

EDIT: Итак, учитывая «Учитывая набор из N идентификаторов друзей, получите все открытые сессии или последнюю сессию для всех этих друзей». Вот запрос, который следует протестировать перед введением новых структур

Сеансов (SessionID, Пользователь, Контекст, Начало, Конец)

SELECT *
FROM Sessions s
WHERE s.End IS NULL 
      AND s.User IN (:friendsList)
UNION ALL
SELECT *
FROM Sessions s
WHERE s.User NOT IN (SELECT User 
                     FROM Sessions s2
                     WHERE s2.User IN (:friendsList)
                           AND s2.End IS NULL)
      AND s.User IN (:friendsList)          
      AND s.End IN (SELECT MAX(End) 
                    FROM Sessions s2 
                    WHERE s2.User = s.User)

Есть и другие способы написания вышеприведенного, чтобы попытаться помочь оптимизатору, в частности, если ваша БД поддерживает CTE, вышеприведенное можно переписать более эффективно.

Примечания: :friendsList - список пользователей, которые являются друзьями.
Кроме того, я предполагаю, что для открытых сессий значение NULL равно End для открытых сессий. Возможно, вы уже выбрали какой-то другой подход (возможно, у вас есть поле, обозначающее его; или есть две таблицы, одна для открытых сессий, одна для закрытых)

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

0 голосов
/ 28 февраля 2011

Ваша основная проблема заключается в том, что необходимая информация распределяется по двум базам данных. Таким образом, вы получаете список друзей и проходите через них.

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

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

Мне это кажется очень неграмотным, но я постоянно этим занимаюсь.

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

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

0 голосов
/ 28 февраля 2011

почему бы не кешировать объекты? Вам не нужно нажимать на БД.

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