В прошлом (до SQLServer 2005 и до LINQ) при работе с такого рода структурой (или более общий случай направленного ациклического графа, реализованного с помощью таблицы соединений, чтобы элементы могли иметь более одного «родителя») Я либо сделал это, загрузив весь граф в память, либо создав в таблице базы данных обновленную тигром таблицу, которая кэшировалась в отношениях предка и потомка.
У каждого из них есть свои преимущества, и выигрыш зависит от частоты обновления, сложности объектов, не связанных с отношениями родитель-потомок, и частоты обновления. В общем, загрузка в память позволяет быстрее выполнять индивидуальный поиск, но при большом графике он не масштабируется также из-за объема памяти, используемого каждым веб-сервером (здесь «каждый», потому что ситуации с веб-фермерским хозяйством - это ситуация, когда элементы, кэшированные в памяти, создают дополнительные проблемы), а это означает, что вам нужно быть очень осторожным в том, как все синхронизировано, чтобы противодействовать этому эффекту.
Третий доступный вариант - поиск предков с помощью рекурсивного CTE:
CREATE VIEW [dbo].[vwCategoryAncestry]
AS
WITH recurseCategoryParentage (ancestorID, descendantID)
AS
(
SELECT parentID, id
FROM Categories
WHERE parentID IS NOT NULL
UNION ALL
SELECT ancestorID, id
FROM recurseCategoryParentage
INNER JOIN Categories ON parentID = descendantID
)
SELECT DISTINCT ancestorID, descendantID
FROM recurseCategoryParentage
Предполагается, что корневые категории обозначены нулевым parentID.
(Мы используем UNION ALL, так как в любом случае мы собираемся потом ВЫБРАТЬ DISTINCT, и таким образом у нас будет одна операция DISTINCT вместо ее повторения).
Это позволяет нам использовать подход с использованием справочной таблицы без избыточности этой денормализованной таблицы. Компромисс эффективности, очевидно, отличается и, как правило, хуже, чем с таблицей, но не очень (небольшое попадание при выборе, незначительное усиление при вставке и удалении, незначительное увеличение пробела), но гарантия правильности выше.
Я проигнорировал вопрос о том, где LINQ вписывается в это, поскольку компромиссы во многом одинаковы независимо от того, как это запрашивается. LINQ может играть лучше с «таблицами», которые имеют отдельные первичные ключи, поэтому мы можем изменить предложение select на SELECT DISTINCT (cast(ancestorID as bigint) * 0x100000000 + descendantID) as id, ancestorID, descendantID
и определить его в качестве первичного ключа в атрибуте [Column]
. Конечно, все столбцы должны быть указаны как сгенерированные БД.
Edit. Еще немного о компромиссах.
Сравнение подхода CTE с поиском в базе данных:
Pro CTE:
- Код CTE прост, в приведенном выше виде представлен весь дополнительный код БД, который вам нужен, а необходимый C # идентичен.
- Код БД находится в одном месте, вместо таблицы и триггера в другой таблице.
- Вставляет и удаляет быстрее; это не влияет на них, в то время как триггер делает.
- Несмотря на семантическую рекурсию, планировщик запросов понимает и может справиться с ним, поэтому обычно (для любой глубины) он реализуется всего за два сканирования индекса (вероятно, кластеризованных), двух облегченных катушек, конкатенации и отчетливый вид, а не во множестве сканов, которые вы можете себе представить. Так что, хотя сканирование, конечно, более тяжелое, чем простой просмотр таблиц, оно далеко не так плохо, как кажется на первый взгляд. Действительно, даже характер этих двух сканирований индекса (одна и та же таблица, разные строки) делает его менее дорогим, чем вы могли подумать, читая это.
- Очень просто заменить это поиском по таблице, если последующий опыт доказывает, что это будет путь.
- Таблица поиска по своей природе денормализует базу данных. Помимо проблем с чистотой, «неприятный запах» означает, что это нужно будет объяснить и оправдать любому новому разработчику, так как до этого он может просто «выглядеть неправильно», и его инстинкты отправят их в погоню за диким гусем, пытающуюся устранить это.
Таблица поиска Pro:
- Хотя выбор CTE быстрее, чем можно себе представить, поиск по-прежнему выполняется быстрее, особенно если он используется как часть более сложного запроса.
- Хотя CTE (и ключевое слово WITH, используемое для их создания) являются частью стандарта SQL 99, они относительно новы, и некоторые разработчики их не знают (хотя я думаю, что этот конкретный CTE настолько прост для чтения, что он считаетсяв любом случае, как хороший пример обучения, так что, возможно, это действительно про CTE!)
- Хотя CTE являются частью стандарта SQL 99, они не реализуются некоторыми базами данных SQL, включая более старые версии SQLServer (которыепо-прежнему используется), что может повлиять на любые усилия по переносу.(Хотя они поддерживаются Oracle, и Postgres среди других, так что на данный момент это может и не быть проблемой).
- Это довольно легко заменить позже версией CTE, если последующий опыт подсказывает, что вам следует.
Сравнение (обоих) вариантов db-heavy с кэшированием в памяти.
Pro In-Memory:
- Если ваша реализация действительно не отстой, это будет намного быстрее, чем поиск в БД.
- Это делает возможной некоторую вторичную оптимизацию после этого изменения.
- Переход с БД на оперативную память достаточно сложен, еслипоследующее профилирование показывает, что путь в память - это путь.
Pro Querying DB:
- Время запуска может быть очень медленным при использовании в памяти.
- Изменения в данных намного проще.Большинство пунктов являются аспектами этого.Действительно, если вы пойдете по маршруту в памяти, тогда вопрос о том, как обрабатывать изменения, делающие недействительной кэшированную информацию, становится совершенно новой постоянной проблемой на протяжении всего срока жизни проекта, а не тривиальной вообще.
- Есливы используете в памяти, вам, вероятно, придется использовать это хранилище в памяти даже для операций, где оно не имеет значения, что может усложнить его совместимость с остальным кодом доступа к данным.
- Нет необходимости отслеживать изменения и обновлять кеш.
- Нет необходимости гарантировать, что каждый веб-сервер в решении для веб-фермы и / или веб-сада (определенный уровень успеха потребует этого) точнота же степень свежести.
- Аналогичным образом, степень масштабируемости для разных машин (насколько можно увеличить производительность на 100%, удваивая число веб-серверов и ведомых устройств БД).
- Прив памяти использование памяти может стать очень высоким, если либо (а) количество объектов велико, либо (б) размеробъекты (поля, особенностроки, коллекции и объекты, которые сами имеют жало или коллекцию).Возможно, «нам нужен больший веб-сервер» объем памяти, и это касается каждой машины в ферме.7а.Такое интенсивное использование памяти особенно похоже на продолжение роста по мере развития проекта.
- Если изменения не приведут к немедленному обновлению хранилища в памяти, решение в памяти будет означать, что представление, используемое людьми вплата за администрирование этих категорий будет отличаться от того, что видят клиенты, до тех пор, пока они не будут синхронизированы.
- Повторная синхронизация в памяти может быть очень дорогой.Если вы не очень умны с этим, это может вызвать случайные (для пользователя) огромные скачки производительности.Если вы сообразительны с этим, это может усугубить другие проблемы (особенно с точки зрения поддержания разных машин на одинаковом уровне свежести).
- Если вы не сообразительны в оперативной памяти, эти всплески могутнакапливать, ставя машину в долгосрочное зависание.Если вы умны, избегая этого, вы можете усугубить другие проблемы.
- очень трудно перейти из оперативной памяти к попаданию в БД, если это окажется правильным решением.
Ничто из этого не опирается со 100% -ной уверенностью на то или иное решение, и я, конечно, не собираюсь давать четкий ответ, поскольку это преждевременная оптимизация.Что вы можете сделать a priori , так это принять разумное решение, которое, вероятно, будет оптимальным решением.Независимо от того, что вы идете, вы должны профиль после, особенноесли код окажется узким местом и, возможно, изменится.Вы также должны делать это в течение всего срока службы продукта, так как как изменения в коде (исправления и новые функции), так и изменения в наборе данных, безусловно, могут изменить оптимальный вариант (на самом деле, он может меняться от одного к другому, а затем возвращаться кпредыдущий, на протяжении всей жизни).Вот почему я включил соображения о простоте перехода от одного подхода к другому в приведенном выше списке плюсов и минусов.