Здравствуйте, Gattaka, Вы писали:
G>>Если все пользователи связаны со всеми, то установка property на любом узле приведет к добавлению Node_Node всех узлов в поле Node2Id. Что для дальнейшего использования не имеет смысла, так как просто можно запрашивать n.Property вместо джоина к Node_Node.
G>>А если ты приведешь реальные средние значения количества связей User_Node и User_User, то перемножив два числа получим количество считываемых\добавляемых строк. Например для User_Node примерное соотношение 10, а для User_User — 150, то надо будет получить максимум 1500 записей для каждого ID. Размер записи — 8 байт (два ID по 4 байта). То есть 12кб. Если это операция обновления, которая не выполняется 10 раз в секунду, то можно не беспокоиться о скорости. G>Считайте, что все со всеми минус 1000000 каких-то случайных связей между пользователями. Вот такие объемы. Те расчеты, что вы привели — оч. мало.
Тогда нужно логику разворачивать — сохранять каких связей НЕТ. Тогда просто два селекта и except. И добавить\удалить надо будет несколько строк.
G>>А вообще я бы лучше сделал материализованную view вида
G>>
G>>create view UserNodeJunction with schemabinding as
G>>select
G>> un1.NodeId as Node1Id,
G>> un1.UserId as User1Id,
G>> un2.NodeId as Node2Id,
G>> un2.UserId as User2Id
G>>from UserOnNode un1
G>>join User_User uu on uu.User1Id = un1.UserId -- Берем связи пользователя
G>>join UserOnNode un2 on un2.UserId = uu.User2Id
G>>create clustered index PK_UserNodeJunction on UserNodeJunction (Node1Id, User1Id, Node2Id, User2Id)
G>>create index IX_NodeLink on UserNodeJunction (Node1Id, Node2Id)
G>>
G>Ерунда , будет тормозить добавление и удаление связей.
Я не вижу в этом проблемы, ты же и так предлагаешь писать по 70 000 строк на клик мышкой, я предлагаю то же самое.
Добавление связей как часто происходит?
G>>Тогда твой запрос вообще можно было бы во view превратить: G>>
G>>create view NodeWithProperty as
G>>select distinct n.Id, j.Node2Id
G>>from Network_Node n
G>>join UserNodeJunction j on n.Id = j.Node1Id
G>>where n.Property = true
G>>
G>Так джойн и так быстро делается, только при вставке связей вы получите дополнительные расходы.
Ты что-то не договариваешь. У тебя в 4 900 000 000 в таблице User_User и 70 000 в UserOnNode. То есть внутренний джоин дает 2e19 записей, которые потом надо отсортировать, чтобы сделать distinct. На это не хватит памяти никакого сервера. Так что не может этот джоин быстро работать ни при каких раскладах.
Поэтому indexed view выгоднее, хранятся уже отсортированные значения в индексе и distinct будет быстрее.
G>Плюс размер базы разратется в 2 раза. Если сейчас это 36 ГБ табличка — будет 72 ГБ.
И? Важно не то, сколько ты хранишь, а сколько читаешь. Ведь 98% операция чтения.
G>>И если это view индексировать (вытащив distinct на уровень выше), то вовсе пропадет необходимость что-то куда-то вставлять.
G>Нет, я вам просто привел упрощенный пример. Вставлять нужно железно, от этого к сожалению никуда не уйти.
Ты так и не объяснил почему, проблема выглядит надуманной.
G>Было бы супер не вставлять, но печаль... Во-первых тут не одно свойство может приводить к такому, во вторых даже если в результате бага связи попали — исчизать не должны. И масса еще другого...
Как за с материализованными view было бы проще, там меньше чего может сломаться.
Здравствуйте, Gattaka, Вы писали:
G>Здравствуйте, Vladek, Вы писали:
G>Дело в другом, что ОРМ вам дает некотрые удобства по рефакторингу, но при этом много чего заберает. READPAST тот же как сделать? У СУБД куча возможностей и плюшек которые попросту обрезаются... Так может вобще отказаться от плюшек ОРМ?
Какбы некорректно рассматривать недостатки без преимуществ.
Также некорректно рассматривать фичи без сценариев использования.
Я вот даже представить не могу зачем readpast может понадобиться.
G>>>create view UserNodeJunction with schemabinding as
G>>>select
G>>> un1.NodeId as Node1Id,
G>>> un1.UserId as User1Id,
G>>> un2.NodeId as Node2Id,
G>>> un2.UserId as User2Id
G>>>from UserOnNode un1
G>>>join User_User uu on uu.User1Id = un1.UserId -- Берем связи пользователя
G>>>join UserOnNode un2 on un2.UserId = uu.User2Id
G>>>create clustered index PK_UserNodeJunction on UserNodeJunction (Node1Id, User1Id, Node2Id, User2Id)
G>>>create index IX_NodeLink on UserNodeJunction (Node1Id, Node2Id)
G>>>
я что-то поторопился.
Нужно такой запрос делать:
create view Node_Node with schemabinding as
select
un1.NodeId as Node1Id,
un2.NodeId as Node2Id,
count_big(*) as cnt
from UserOnNode un1
join User_User uu on uu.User1Id = un1.UserId
join UserOnNode un2 on un2.UserId = uu.User2Id
group by un1.NodeId, un2.NodeId
create clustered index PK_UserNodeJunction on Node_Node (Node1Id, Node2Id)
Здравствуйте, Gattaka, Вы писали:
G>Но у большинства разработчиков делаются глаза круглыми при словах Table Spool, Nested Loop, кластерный и некластерный индекс чем отличаются.
Про кластерный индекс я знаю, про остальное нет.
G>И не знание этих простых вещей [...]
Ваш вопрос был о минусах переноса логики в хранимые процедуры. Я вам привёл пример минуса — "обычным" fullstack-разработчикам трудно поддерживать такой код. Нужны будут люди с хорошим знанием SQL.
Юниттестов для SQL-кода в том проекте, разумеется, не было.
Здравствуйте, wildwind, Вы писали:
W>А у тебя была возможность сравнить с таким же проектом, но с логикой на клиенте? Может там было бы еще хуже.
Нет, других подобных проектов я пока не видел. Вполне может быть что было бы и ещё хуже.
W>Это не проблема подхода, это проблема управления проектом. Был профессиональный SQL-разработчик, потом на нем решили сэкономить. Вот и результат.
Может и так. Не знаю. Я озвучил один из минусов такого подхода — для поддержки проекта потребуется профессиональный SQL-разработчик, у обычных C#-разработчиков поддержка такого проекта займёт куда больше времени и усилий.
Здравствуйте, Gattaka, Вы писали:
G>В БД может хранится как угодно, смысл ОРМ в том чтобы замепить доменную сущность на таблицу или таблицы не доставляя головной боли.
К сожалению, с БД эта благоглупость не прошла. Число задач, где это полноценно срабатывает стремится к нулю. Фактически, это работает только, если нужно обеспечить ввод пользователя, точно совпадающий со структурой таблиц.
G>Вот собственно несостоятельность этой идеалистической картины я и пытаюсь донести. Причем идею работы с доменными объектами без привязки к БД я одобряю. Она здравая и интересная, но не реализованная еще...
Фигли там реализовывать? Вопрос в нужности.
G>И есть еще подход типа Dapper или linq2db — это вобще не ORM, это всего лишь способ писать на C# запросы к базе данных. Эдакий SQL без SQL... По мне так лучше сразу писать на SQL и не парится, к чему эта прослойка, которая лишь добавляет сложности, как правило не все умеет. Аргумент интеллисенс и упрощенного рефакторинга ИМХО того не оправдывает...
Аргумент интеллисенс и не упрощённый, а полноценный рефакторинг — это вообще-то мегафича linq. Тот, кто утверждает обратное просто не в теме.
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, gandjustas, Вы писали:
G>>Проблема в том, что данных дофига — 36 ГБ и проекция вам не поможет. G>Проблема в том, что ты проблему не можешь объяснить, ты уже четвертый пост ходишь вокруг и даже запрос не привел и user flow объяснить не можешь нормально. У тебя есть только отмазка "данных много".
36 ГБ это не много. Это ерунда. На моём текущем проекте столько данных генерируется дня за 3-4. При этом LINQ используется тотально.
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, wildwind, Вы писали:
W>В общем виде я бы сказал так. Пока ваше приложение достаточно простое, некритичное к производительности и полностью владеет базой, используйте ORM. Но если систему планируется масштабировать (или просто возможностей ORMа перестанет хватать), нужно быть готовым перенести часть логики в хранимки.
Я бы сказал так. Увидели хранимку — отбейте железной ленейкой руки тому, кто это сделал. Не существует ни одного реального аргумента, почему ХП должны ещё жить. И уж точно масштабируемость тут ни при чём.
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, Gattaka, Вы писали:
G>Ну супер! На самом деле я ожидал что вы dapper предложете или linq2db, но тоже не катит, т.к. работу с блокировками в многопользовательском режиме лучше возложить на SQL, в частности READPAST и т.п. подобные штуки понадобятся...
linq2db вполне себе поддерживает table hints.
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, Gattaka, Вы писали:
G>Дело в другом, что ОРМ вам дает некотрые удобства по рефакторингу, но при этом много чего заберает. READPAST тот же как сделать? У СУБД куча возможностей и плюшек которые попросту обрезаются... Так может вобще отказаться от плюшек ОРМ?
from p in db.Parent.With("READPAST")
select p;
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, Gattaka, Вы писали: G>Это все здорово, но только разница между таким использованием ОРМ и написанием запросов на SQL сводится к тому какой язык мы используем. Ну по факту C#, т.к. многие не знают толком SQL. Суть ОРМ ведь не в этом. Идея такая, что мы работаем с нашими доменными объектами как обычно, будто базы данных нет.
Вы неправильно поняли идею ОРМ. Вот конкретно эта идея — порочная по самой своей природе. Она гарантирует хреновую производительность и адское неудобство в разработке.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Gattaka, Вы писали: HBM>>только что касается выборки реляционных данных. HBM>>в остальном мы получаем кучу проблем. HBM>>- реализовывать сложную логику в разы сложнее и код становится страшным G>Сказки про страшный код.
К сожалению, это не сказки. В SQL отвратительные возможности по декомпозиции. Появление table-valued функций немножко улучшило ситуацию, но недостаточно радикально.
Поэтому типичная бизнес-логика на SQL — это бесконечные повторы однообразных оборотов типа "where (t1.OwnerID = @userID or t1.GroupIP = @userGroupId) inner join ... ... where (t2.OwnerID = @userID or t2.GroupIP = @userGroupId)". Это тяжело отлаживать (к примеру, для SQL Server интерактивный отладчик появился уже после того, как я перестал на нём программировать, а для остальных СУБД, как я понимаю, такого до сих пор нет), легко ошибиться, и нет статической проверки корректности компилятором.
Поэтому при прочих равных я предпочту эту бизнес-логику видеть написанной на Linq, где можно с одной стороны писать декларативные стейтменты в стиле SQL, не задуряясь порядком обхода джойнов, а с другой стороны есть все плюшки декомпозиции современного ЯП.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Gattaka, Вы писали: G>Не верите. Окей, вот вам реальный бизнес сценарий. Есть компьютерная сеть в которой есть сетевые узлы и пользователи. Они имеют связи между собой, мы это все храним в базе и даем возможность через наше приложение админить. G>Таблицы User(Id, Name, Property), Network_Node(Id, Name, Property), User_User(User1Id, User2Id), Network_Node(Node1Id, Node2Id), UserOnNode(NodeId, UserId) G>В нашей базе 70000 узлов по одному пользователю на узле (для простоты). Пользователи связаны все со всеми. Для некоторый (приблизительно половины) узлов был проставлен признак Prorepry — теперь нужно добавить для таких узлов на которых есть зарегестрированные связанные пользователи . Особенно посчитайте размер таблицы User_User, это приятный момент
Структура базы не имеет значения. Вы просто мыслите категориями ORM девяностых годов, когда теоретики верили в преодоление Impedance mismatch и прочий булшит.
Речь о том, что
а) поднимать никакие данные в память не нужно. Ни в клиента, ни в аппсервер в трёхзвенке. В памяти нужны только те данные, которые там используются.
б) ХП в таких случаях работают ничуть не лучше, чем обычный SQL batch. Я встречал множество людей, искренне полагающих себя знатоками SQL, которые вообще не знали о возможности отправить на сервер несколько стейтментов кряду.
в) писать голый SQL в таких случаях — занятие малоперспективное. Не тот это язык, на котором удобно писать что-то длиннее пары килобайт.
г) поэтому правильный ответ — это использование современных средств типа linq2sql.
Пишете на нём тот самый SQL, который вы хотели запихать в ХП. Смотрите на него, убираете однообразные повторы путём выноса во вспомогательные процедуры/функции. Наслаждаетесь статическим контролем компилятора и возможностями рефакторинга. Профит!
А через некоторое время замечаете ещё один волшебный бонус: гарантия непротиворечивости версии базы и аппсервера. Расхождений в коде ХП между стейджингом и продакшном больше нет как класса.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Gattaka, Вы писали: G>Дело в другом, что ОРМ вам дает некотрые удобства по рефакторингу, но при этом много чего заберает. READPAST тот же как сделать? У СУБД куча возможностей и плюшек которые попросту обрезаются... Так может вобще отказаться от плюшек ОРМ?
То, что вы называете "ОРМ" — это ублюдочное порождение воспалённого воображения теоретиков девяностых. Выбросьте его немедленно! По сравнению с ним даже логика в ХП выглядит прилично.
Но в 2016 есть ещё более хорошие средства написания логики, чем попытки превратить SQL Server в сервер приложений.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, Gattaka, Вы писали: G>>Дело в другом, что ОРМ вам дает некотрые удобства по рефакторингу, но при этом много чего заберает. READPAST тот же как сделать? У СУБД куча возможностей и плюшек которые попросту обрезаются... Так может вобще отказаться от плюшек ОРМ? S>То, что вы называете "ОРМ" — это ублюдочное порождение воспалённого воображения теоретиков девяностых. Выбросьте его немедленно! По сравнению с ним даже логика в ХП выглядит прилично. S>Но в 2016 есть ещё более хорошие средства написания логики, чем попытки превратить SQL Server в сервер приложений.
Вы про что конкретно? Может быть еще эти варианты не озвучивали? linq2db?
Здравствуйте, Gattaka, Вы писали: S>>Но в 2016 есть ещё более хорошие средства написания логики, чем попытки превратить SQL Server в сервер приложений. G>Вы про что конкретно? Может быть еще эти варианты не озвучивали? linq2db?
Ага. Там и с table hint никаких проблем нет, и вообще в целом всё хорошо — нет навязанных услуг типа rich domain model и прочей наркомании.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
G>>Вы про что конкретно? Может быть еще эти варианты не озвучивали? linq2db? S>Ага. Там и с table hint никаких проблем нет, и вообще в целом всё хорошо — нет навязанных услуг типа rich domain model и прочей наркомании.
То есть вы сам подход rich domain model не одобряете? Вроде бы там все стройно и гладко получается — многим нравится в сравнении с анемичной моделью. Лично по мне так, все бы хорошо если бы была хоть одна нормальная ОРМка, а написать такую ОРМ мне даже не представляется возможным... настолько это сложно.
Здравствуйте, Gattaka, Вы писали:
G>Здравствуйте, Sinclair, Вы писали:
G>>>Вы про что конкретно? Может быть еще эти варианты не озвучивали? linq2db? S>>Ага. Там и с table hint никаких проблем нет, и вообще в целом всё хорошо — нет навязанных услуг типа rich domain model и прочей наркомании. G>То есть вы сам подход rich domain model не одобряете? Вроде бы там все стройно и гладко получается — многим нравится в сравнении с анемичной моделью.
Поймите, хорошая абстракция — это такая абстракция, которая игнорирует несущественные детали.
Делать вид, что ваши объекты — "вот они", на расстоянии одного вызова метода — неконструктивно.
Оказывается, что код вида
foreach(var employee in Db.GetAllEmployees())
{
if (employee.CurrentPosition = position)
{
var c = Db.FindContractByEmployee(employee);
c.AdjustSalary(c.CurrentSalary + c.CurrentSalary * 0.10, effectiveDate);
}
}
легко писать, но он чудовищно тормозит. Люди тратят годы на оптимизацию подобного булшита, пользователи привыкают к операциям, которые длятся сутки и часто откатываются с conflicting change из-за optimistic locking, и так далее. (Ещё веселее, когда Rich Model Fan добавляет метод AdjustSalary прямо к Employee, и прячет логику по подгрузке контракта прямо тудысь. А полные фанаты вообще считают, что подобный код не имеет права ходить вне объектов, и считают само собой разумеющимся метод EmployeePosition.AdjustSalary(int increasePercent, Date effectiveDate). )
При этом тот же самый код прекрасно летает на моках, в которых Db заменён на in-memory collection.
Хорошая абстракция должна учитывать, что стоимость каждого FindContractByEmployee — запредельна по сравнению с временем исполнения кода на клиенте. Что фильтрация по position на клиенте — это факап. Что вообще идея поднимать в память клиента employee, который тебе нафиг не нужен — это лишнее напряжение канала между базой и апп-сервером, а удержание транзакции на такие длинные сроки — должностное преступление.
Ещё важнее то, что вот этот вот чудестный код меняется раз в месяц вместе с бизнес требованиями, а структура данных почти неизменна. При этом у нас нет механизма по отделению изменений в логике от изменений в структуре.
В анемик-модели у нас были бы lean entities типа Employee и Contract, а AdjustSalary был бы методом отдельного stateless-модуля, написанного с массовым использованием linq.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, IT, Вы писали:
IT>Я бы сказал так. Увидели хранимку — отбейте железной ленейкой руки тому, кто это сделал. Не существует ни одного реального аргумента, почему ХП должны ещё жить. И уж точно масштабируемость тут ни при чём.
Странно слышать столь категоричные утверждения от человека с большим опытом. Хранимки это API к базе, один из видов. А их придумано немало, помимо голого SQL: view с триггерами, MDX, REST HTTP, HandlerSocket и т.д.; и завтра придумают еще. У каждого своя ниша и свои особенности. Для меня "хранимки не должны жить" звучит столь же абсурдно, как "не используйте TCP/IP, только raw sockets".
Ты имел дело с базами, используемыми несколькими приложениями, да еше написанными на разных языках? Наверняка имел. Там не было ни одной хранимки?
А масштабируемость вот при чем. В процессе масштабирования обязательно наступает период, когда нужно выжать из СУБД максимум, чтобы оттянуть апгрейд железа. Начинается поиск вещей, которые эффективнее выполнить внутри базы, чем гонять данные по сети и растягивать транзакции. И ради эффективности приходится жертвовать читаемостью, сопровождаемостью и еще чем-то из чеклиста "идеальный проект". А хранимки позволяют это смягчить. Если их не использовать, то получишь те же "хранимки", но обфусцированные, хранящиеся в коде и создаваемые каждый раз на время выполнения. Что еще хуже.
Кроме того, хранимки могут содержать не только бизнес логику. Более того, от некоторых бизнес-приложению ни жарко, ни холодно. Как то:
триггеры и общие алгоритмы, используемые в них;
RLS;
поддержка хитрых схем репликации, ETL и прочей интеграции с другими системами;
поддержка администриративных процедур;