Как спроектировать базу данных для хранения отсортированного списка?

42

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

  1. Вставить (x) - Вставить запись x в таблицу
  2. Удалить (x) - удалить запись x из таблицы
  3. Before (x, n) - вернуть 'n' записей, предшествующих записи x в отсортированном списке.
  4. После (x, n) - вернуть 'n' записей, следующих за записью x в отсортированном списке.
  5. First (n) - вернуть первые 'n' записей из отсортированного списка.
  6. Last (n) - вернуть последние 'n' записи из отсортированного списка.
  7. Сравнение (x, y) - Учитывая две записи x и y из таблицы, найдите, если x> y.

Простой метод, который я мог бы придумать, - это сохранить в таблице какой-то атрибут «ранг» и выполнить запрос путем сортировки по этому атрибуту. Но в этом методе вставка / изменение записи с рангом становится дорогостоящей операцией. Есть ли лучший метод?

В частности, я хочу реализовать таблицу с помощью Amazon SimpleDB. Но общий ответ для реляционной базы данных также должен быть полезным.

Обновление профиля нагрузки:

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

Если есть 100 000 активных пользователей (супер оптимизм: P), то моя очень приблизительная оценка в день будет

500 тыс. Выбирает, 100 тыс. Вставляет и удаляет, 500 тыс. Обновлений

Я ожидаю, что таблица вырастет в общей сложности до 500 тысяч.

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

Chitti
источник
Уточните немного о вашем ожидаемом профиле нагрузки. Сколько выбирает / вставляет / обновляет в день? Какие операции вы хотите оптимизировать больше всего? Насколько большой вы ожидаете, что таблица будет расти в день или в целом?
Ник Чаммас
Это для рейтинга игроков? В любом случае, я обновил свой ответ ниже с отзывами, основанными на вашем профиле предполагаемой нагрузки.
Ник Чаммас
Нет, это не доска рейтинга игроков.
Читти
Какой подход вы в конечном итоге использовали?
Ник Чаммас
Я даже не уверен в том, что здесь спрашивают или что вам не нужно делать из списка вещей, которые вам нужно сделать.
Эван Кэрролл

Ответы:

22

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

Если это произвольное свойство данных, то , что должно быть сохранено в качестве столбца в таблице рекордов. Предполагая, что Amazon SimpleDB похожа на типичную СУБД, вы можете затем проиндексировать этот столбец и быстро удовлетворить все вышеперечисленные запросы с помощью соответствующей стратегии индексации. Это нормально для РСУБД.

Учитывая, что вы ожидаете высокую активность вставки и обновления, а также относительно высокую активность чтения, я рекомендую сделать следующее:

  • Сгруппируйте таблицу по рангу, особенно если подавляющее большинство ваших запросов против ранга. Если нет или если выбор ключа кластеризации недоступен в SimpleDB, просто создайте индекс с рангом в качестве ведущего столбца. Это удовлетворит запросы 3-6.
  • Индекс сначала для записи, а затем для ранга (или, в мире SQL Server, просто для записи и INCLUDE-ing ранга, или просто для записи, если вы кластеризовались по рангу) будет удовлетворять запросу 7.
  • Операции 1 и 2 могут быть оптимизированы путем правильного распределения данных (т. Е. Установки FILLFACTORв SQL Server). Это особенно важно, если вы группируете по рангу.
  • По мере того как вы вставляете или обновляете ранги, сохраняйте как можно больший промежуток между номерами рангов, чтобы минимизировать вероятность того, что вам нужно будет переназначить существующую запись для размещения ранга или обновления. Например, если вы ранжируете свои записи с шагом 1000, вы оставляете достаточно места для примерно половины такого количества изменений и вставляете с минимальной вероятностью, что вам потребуется пересмотреть рейтинг записи, не связанной непосредственно с этими изменениями.
  • Каждую ночь пересортируйте все записи, чтобы сбросить разрывы между ними.
  • Вы можете настроить частоту массового повторного ранжирования, а также размер разрыва рангов, чтобы соответствовать ожидаемому количеству вставок или обновлений относительно количества существующих записей. Так что, если у вас есть 100K записей и вы ожидаете, что ваши вставки и обновления будут составлять 10%, оставьте достаточно места для 10K новых рангов и переназначьте их по ночам.
  • Повторное ранжирование 500K записей - это дорогостоящая операция, но она проводится один раз в день или неделю в нерабочее время для такой базы данных. Массовое повторное ранжирование в нерабочее время для поддержания разрыва рангов - это то, что избавляет вас от необходимости переоценивать множество записей для каждого обновления ранга или вставлять его в обычные и часы пик.

Если вы ожидаете 100K + чтения для таблицы размером 100K +, я не рекомендую использовать метод связанного списка. Это не будет хорошо масштабироваться до этих размеров.

Ник Чаммас
источник
Ранги могут быть изменены. Я ожидаю, что ряды будут постоянно меняться, и новые записи будут постоянно вставляться. Меня беспокоит случай, когда я вставляю новый элемент с рангом, тогда необходимо изменить ранги всех записей ниже новой записи в порядке сортировки. Разве это не дорогая операция, когда в моей базе данных тысячи записей?
Читти
@chitti - Ах, это проблема. Вы можете распределить свои рейтинги (например, 0, 1000, 2000, 3000, ...) и периодически переоценивать все записи по мере заполнения пробелов в рейтинге. Это не будет масштабироваться, если вы ожидаете намного больше, чем несколько десятков тысяч записей.
Ник Чаммас
1
@chitti - Это довольно забавно, на самом деле. Это именно та проблема, с которой сталкиваются ядра СУБД при индексации данных, потому что они упорядочивают их и переупорядочивают по мере добавления или изменения данных. Если вы посмотрите вверх, FILLFACTORто увидите, что в основном это означает создание дополнительного пространства для записей в индексе, точно так же, как разрывы рангов, которые я описал, создают пространство для изменений рангов и вставок.
Ник Чаммас
2
Спасибо за обновленный ответ. «Ранг» - это произвольное свойство моих данных. Я почти уверен, что мне нужен пользовательский индексный столбец. Проверьте эту ссылку SO с похожим вопросом. В верхнем ответе содержатся рекомендации о том, как обрабатывать такой столбец ранга.
Читти
@chitti - Принятый ответ на этот ТАКИЙ вопрос великолепен. Он предлагает тот же подход, который я подробно описал здесь, с дополнительным предложением использовать десятичные числа вместо целых, чтобы значительно расширить вашу гибкость в назначении и изменении рангов. Отличная находка.
Ник Чаммас
13

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

Альтернативный подход заключается в том, чтобы смоделировать записи в виде связанного списка, используя столбец рефлексивного внешнего ключа «предшественника» в таблице:

ID   setID   item       predecessor
---  ------  ------     ------------
1    1       Apple      null
2    1       Orange     1
3    2       Cucumber   null
4    1       Pear       2
5    1       Grape      4
6    2       Carrot     3

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

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

bpanulla
источник
2
Модель связанного списка аккуратна. Чтобы получить такую ​​иерархию по порядку в SQL Server, вы должны использовать рекурсивный CTE .
Ник Чаммас
Однако построение этой иерархии было бы довольно дорого для высокого стола. Преимущество состоит в том, что изменения ранга / вставки / и т.д. могут быть сделаны легко. В зависимости от ожидаемого профиля нагрузки Читти, это может быть лучшим подходом.
Ник Чаммас
Параметр связанного списка выглядит как лучшая идея для всех операций, кроме сравнения. Любая идея, как я мог бы реализовать Сравнение без необходимости прослеживать путь между двумя сравниваемыми элементами?
Читти
Если у вас есть идентификаторы элементов, я думаю, что Compare () будет простым, если я не пойму, что вы имели в виду под Compare (). Когда вы сказали: «найти, если х> у», вы имели в виду «найти, если х предшествует у»? Я не могу представить, чтобы это было легко без пользовательского индекса или хранимой процедуры, которая обошла бы список (или этой интересной функции CTE, упомянутой @Nick).
bpanulla
5
Этот тип решения также приближается к графической модели данных ( en.wikipedia.org/wiki/Graph_theory ). Система хранения, оптимизированная для хранения узлов и ребер графа, может быть лучшим решением, чем СУБД. Тройные и квадро-хранилища и графические базы данных, такие как Neo4J, довольно хороши в этом.
bpanulla
6

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

Джоэл Браун
источник
2
Что если «свойства, используемые для вычисления ранга» являются произвольными? Например: набор записей корзины покупок, которые переупорядочиваются в зависимости от произвольных действий пользователя.
Читти
Когда вы говорите, что звание является произвольным, что вы имеете в виду? Должен быть алгоритм, который вы используете, чтобы вычислить, каким должен быть ранг. Например: «На основе записей корзины покупок» - На основании как? В базе данных должно быть что-то, что является драйвером для расчета ранга. Это может быть комбинация нескольких вещей, но эти вещи должны как-то храниться в таблице клиента или в таблицах, связанных с клиентом. Если он находится в данных, то вы можете создать функцию, которая его вычисляет. Если вы можете рассчитать его, вы можете сохранить его и индексировать его.
Джоэл Браун
Допустим, нам нужно поддерживать порядок товаров в корзине, и пользователь может «произвольно» изменить порядок, используя веб-интерфейс. Как бы вы хранили такой список элементов в базе данных и как бы вы поддерживали порядок сортировки?
Читти
Если я вас правильно понимаю, под «произвольным изменением» порядка элементов в корзине вы подразумеваете, что пользователь может перетаскивать элементы вверх и вниз по списку и перетаскивать их туда, куда они хотят. Я думаю, это кажется мне немного надуманным. Зачем пользователям это делать? Если бы они могли сделать это, они бы сделали это много? Действительно ли использование простой последовательности товаров в корзине так сильно влияет на производительность? Мне кажется, что порядковый номер от одного до количества товаров в корзине + FK к заказу даст вам нужный вам индекс. Просто обновляйте предметы, когда вас тянут.
Джоэл Браун
3
Корзина покупок - это просто пример, который я привел, чтобы показать, что в некоторых случаях «ранг» может быть произвольным. Может быть, это был не лучший пример. Netflix DVD-очередь может быть лучшим примером. Просто ради аргумента представьте себе очередь netflix с 100 тыс. Элементов, которые пользователь может произвольно переупорядочивать, и он делает это каждую минуту. Как бы вы разработали базу данных для хранения этого упорядоченного списка фильмов в этом гипотетическом приложении?
Читти
1

Это ограничения не-СУБД, как simpleDB. Необходимые функции не могут быть реализованы на стороне БД в simpleDB, они должны быть реализованы на стороне программирования / приложения.

Для подобных СУБД требуемые SQL serverфункции являются элементарными по отношению к кластерному индексу.

  • Вставить (x) - Вставить запись x в таблицу> Простая вставка.
  • Удалить (x) - Удалить запись x из таблицы> Простое удаление.
  • Before (x, n) - вернуть 'n' записей, предшествующих записи x в отсортированном списке. > Выберите top n результатов, где x меньше значения и упорядочите по выражению.

  • После (x, n) - вернуть 'n' записей, следующих за записью x в отсортированном списке. > Выберите top n результатов, где x больше значения и упорядочите по выражению.

  • First (n) - вернуть первые 'n' записей из отсортированного списка. > Выберите лучшие n результатов.

  • Last (n) - вернуть последние 'n' записи из отсортированного списка. > Выберите лучшие n результатов после заказа по дес.

  • Сравнение (x, y) - Учитывая две записи x и y из таблицы, найдите, если x> y. > TSQL IF заявление.
StanleyJohns
источник
SimpleDB предоставляет автоматические индексы, сортировку и базовый язык запросов . Моя проблема останется, даже если я выберу СУБД. Проблема заключается в том, что ранжирование данных в моей базе данных изменяется произвольно, и они не могут быть зафиксированы как одно свойство (если я не использую столбец пользовательского ранга), которое можно проиндексировать.
Читти
0

Вот что я использовал для ранжирования моей таблицы Postgres после каждой вставки:

CREATE OR REPLACE FUNCTION re_rank_list() RETURNS trigger AS $re_rank_list$
DECLARE
    temprow record;
    row_idx integer := 1;    
BEGIN
    FOR temprow IN
    SELECT * FROM your_schema.your_list WHERE list_id = NEW.list_id ORDER BY rank ASC
    LOOP
        UPDATE your_schema.your_list SET rank = row_idx * 100 WHERE id = temprow.id;
        row_idx := row_idx + 1;
    END LOOP;
    RETURN NEW;
END;
$re_rank_list$ LANGUAGE plpgsql;


CREATE TRIGGER re_rank_list AFTER UPDATE ON your_schema.your_list_value
    FOR EACH ROW 
    WHEN (pg_trigger_depth() = 0)
    EXECUTE PROCEDURE re_rank_list();

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

отметка
источник