Pull to refresh
57
0
Send message
решается нормализацией данных — имеем отдельно таблицы Location и Airport, и в Airport также имеем foreign key к Location. Просто, оптимизируемо, и — внимание! — логически верно, так как

Так Airport это не Location. А вот Capital это City. А Роза — цветок.

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

То почему произвольными объектами плохо я писал в статье в разделе про наследование и полиморфизм. И как бы я хотел тоже там писал. И уже несколько раз тут отвечал, почему наследование таблиц не подходит.
Сужу исключительно по наличию нескольких типов с одинаковым названием.

В lsFusion просто множественное наследование есть. Это одинаковые классы. Просто так граф в виде дерева выглядит.
Затем же зачем и в Java. Модульность и расширяемость. То есть нужен новый функционал, добавляем новые классы, наследуем, добавляем в абстрактные представления реализацию для этих классов. Иначе спагетти-код как в 1С.

А можно все сделать нормально классами, наследованием и полиморфизмом. Вот пример кусочка дерева классов например:


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

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

Но тут вопрос то в другом. Остаток вам все равно надо хранить и обновлять инкрементально. Иначе система просто ляжет из-за нарушения баланса чтения и записи. Весь вопрос, как это делать. Задать декларативно представление и сказать серверу — теперь это твои проблемы, крутись как хочешь. Или самому разработчику императивно руками обновлять соответствующую таблицу. Не говоря о том что поддержать все случаи изменений очень тяжело, а значит нужно прибегать к разным хакам вроде запретов и перепроведений, о чем я и писал в статье.
При это умалчивается, что в принципе в задаче пересчета остатков заложен конфликт, который приходится разрешать в любой системе

Вы про update conflict'ы? Ну так с ними то как раз SQL сервера более менее сами справляются. От разработчика нужно только перестарт транзакции поддержать (хотя это тоже не такая тривиальная задача). Но это перпендикулярно задаче материализации представлений.
Или мы строго учитываем остатки, или быстро, при этом в быстро еще заложено или быстро пишем, или быстро читаем.

Проблема в том что пользователю надо и то и другое. А дальше вопрос балансов. Грубо говоря пользователь обычно согласен лишние 500мс подождать на проведении документа, но чтобы все остальное летало.
т.е. одну проблему, мы поменяли на другую

Почему поменяли? Решили одну. Ну а вторую значительно упростили. Так, достаточно обновлять поле «остаток у поставщика» как первичные данные в своей таблице, а дальше уже внутри сервер включает свою магию с материализациями, событиями и т.п.
Это обычно если логика достаточно простая (то есть немного сложнее CRUD). Если вам нужно делать группировки, композиции, разбиения, упорядочивания и у вас будут десятки таких показателей на каждой форме, на noSQL это будет очень тяжело сделать.

Собственно в свое время было много холиваров на тему SQL vs noSQL, и, соответственно, много историй, когда людям с noSQL на SQL приходилось переходить из-за невозможности сделать простейший JOIN.
Я имел ввиду, что ок наследование первичных данных (таблиц) вы сделаете, а что с наследованием вычислений (когда не просто return field) предполагается надо делать?
1) Большой кэш для предвычисленных затратных вещей на Redis. Обслуживание кэша не на SQL, а на прикладном языке.

ACID'а не будет, то есть подойдет только для аналитики по большому счету, во всяком случае в бизнес-приложениях.
2) Запуск длительных запросов в виде отдельных скриптов на других языках. Добавление прогрессбаров в конце концов.

Тоже самое что и в пункте 1). Хотя во многих случаях действительно помогает (в lsFusion это асинхронные события).
3) Решение проблемы наследования на уровне ORM — в PHP в Doctrine, точно можно делать красивое наследование. Затем же из PHP работать с сущностями и построитель запросов ORM сам соберет данные из нужных таблиц.

Там в ORM те же и проблемы что в Oracle будут (в статье есть), то есть N+1 и непонятно что делать с таблицами скажем с 3 ключами.
SQL не идеален, но он уже давно под капотом у других языков. И в целом системы, где используется деревянный SQL, сгенерированный ORM или неискушенными в ORM программистами легче поддерживать, чем базу со вьюшками, хранимками и прочими радостями.

Он все же обычно не под капотом, а сбоку. То есть у большинства других языков есть трансляторы в SQL, типа как в LINQ, Hibernate(HQL) или 1С.
Но и на голом SQL где очень важна производительность (банки, финансы, ритейл), тоже много чего пишут. Откуда по вашему у Oracle оборот 10млрд, не как у подложки же под SQL.
Вот и надо говорить о проблемах и альтернативах. Возможно для кого-то некоторое снижение производительности будет приемлемо для уменьшения вероятности ошибки и увеличения удобности разработки.

Проблемы и альтернативы это разные вещи. Хотя в статье как раз есть много и про альтернативы (и как это сделано в lsFusion).

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


Там в том то и дело что их больше чем 2. Я специально все собрал в одну статью, чтобы было видно что их гораздо больше, и что производители СУБД везде смогли накосячить.

Ну и я не говорил, что они чему-то не соответствуют. Тут вот какая ситуация. Смотрите мы когда делали платформу над SQL и компилировали запросы, к нам постоянно приходили разработчики и говорили вот мы тут такую логику нахерачили и у нас что-то тормозит. Ты им отвечаешь, смотрите вот видите запрос, видите план, видите как СУБД косячит. Знаете что они отвечали, а нам какая разница, чьи это проблемы, формируйте такие запросы, чтобы они выполнялись быстро. Понятно что это вопрос бренда. Будь у нас бренд Oracle или MS SQL мы бы всех посылали на три буквы и говорили бы, что «просто вы не так держите», крутитесь сами как хотите. Но нет, нам приходилось решать проблемы СУБД, мы конечно в конце концов все их решили, но сам факт почему это приходилось делать нам, хотя мы могли бы сфокусироваться именно на верхнем уровне, конечно дико раздражал. Собственно так и родилась эта статья.

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

Ну об этом в общем-то и статья.
Разработчик только указывает что-то типа Entity::find()->where(...)->with('relationship').

Ну так я и говорю разработчик делает, а не ORM.
Вызываем логику для каждой сущности

Ну так вы когда вызываете логику каждой сущности у вас N+1 и будет.

Или еще хуже вам эту логику дублировать придется. Сначала чтобы избежать проблемы N+1 докинуть в первый запрос все нужные данные, а потом еще раз повторить эту логику в ORM с обработкой этих данных (дублирование конечно не полное будет, но процентов на 80). Ну и вариантов выстрелить в ногу, если забыли, не те данные считали и т.п.

Но вообще конкретно эта статья не про ORM, тут как раз про то когда бизнес-логику на SQL делают — в банках и других финансах, ритейле это очень распространено.
Я у них ACID'а что-то не вижу, а учитывая, что они позиционируются как analytics database, то возможно его там и нет. А я имел ввиду Distributed SQL сервера именно с ACID.
Они не входят в стандарт, про что вам сразу и сказали. И более того они не декларативны по своей сути.


Стандарт SQL вообще очень забавная штука. Они UPDATE и агрегирующие функции стандартизировать не могут. Ну я нигде и не говорил про стандарт, это чисто практическая статья, а хранимки и триггеры при разработке на SQL используются очень часто.

В каком месте и каким боком они к SQL подходят и как они туда ложатся? Единственное применение наследование которое я использовал в том же PostgreSQL это для секционирования таблиц, для другого оно как-то плохо применимо. Приведите хоть один вариант использования когда это прям дает какой-то внятный профит. Я вот не припомню ни одного применения где полиморфизм и наследование давали какой-то внятный эффект. Более того чем дальше тем больше в нем разочаровываются и откатываются к функцинальщине.

Функциональщина перпендикулярна наследованию и полиморфизму. А вообще возьмите крупный проект на C++ или Java и посчитайте там количество extends /: и abstract / virtual.
Так можно делать только с первичными данными (то есть с таблицами). Запросы (то есть VIEW) в реализацию добавлять нельзя (в смысле что VIEW наследовать от таблиц / друг от друга). То есть это как если бы вам в классическом ООП дали бы abstract, и сказали что в реализации можно только return field делать.

Плюс так можно делать при индуктивном задании логики. При дедуктивном, когда создаются таблицы со своими именами полей, а потом функционал надо обобщить в абстрактный так тоже не сработает.

Но вообще надо было конечно в примере GROUP BY написать или что-то по сложнее, чтобы вопросов не было.
В пункте Проблема N+1 есть уточнение про расширения вроде.

А полиморфизм с наследованием это очень даже декларативные концепции. Я бы даже сказал более декларативные чем SQL, если декларативностью вообще меряться можно. И я в статье написал зачем:

возможности эффективно декомпозировать задачи, а также не наращивать технический долг по мере роста сложности системы
Тут фишка видимо в том, что lateral это гарантировано nested loop join.

То есть вот такой запрос:
SELECT SUM(cc.ls) 
  FROM Product pr
  LEFT JOIN lateral ( SELECT SUM(shipment/1000) AS ls
            FROM shipmentDetail s
		    where s.product = pr.id
            ) cc on true
        ;

Дает вот такой план:
"Aggregate  (cost=40238877.72..40238877.73 rows=1 width=32) (actual time=18344.109..18344.109 rows=1 loops=1)"
"  ->  Nested Loop Left Join  (cost=804.71..40238752.71 rows=50001 width=8) (actual time=0.615..18335.300 rows=50001 loops=1)"
"        ->  Seq Scan on product pr  (cost=0.00..819.01 rows=50001 width=4) (actual time=0.014..12.254 rows=50001 loops=1)"
"        ->  Aggregate  (cost=804.71..804.72 rows=1 width=8) (actual time=0.365..0.366 rows=1 loops=50001)"
"              ->  Bitmap Heap Scan on shipmentdetail s  (cost=6.04..803.68 rows=207 width=4) (actual time=0.042..0.340 rows=200 loops=50001)"
"                    Recheck Cond: (product = pr.id)"
"                    Heap Blocks: exact=9986478"
"                    ->  Bitmap Index Scan on shipmentdetail_p_s  (cost=0.00..5.99 rows=207 width=0) (actual time=0.020..0.020 rows=200 loops=50001)"
"                          Index Cond: (product = pr.id)"
"Planning Time: 0.194 ms"
"Execution Time: 18344.192 ms"


А его то как раз эффективнее вот так выполнять:
SELECT SUM(cc.ls)
  FROM Product pr
  LEFT JOIN ( SELECT product, SUM(shipment/1000) AS ls
            FROM shipmentDetail s
		    GROUP BY product
            ) cc on cc.product = pr.id
        ;

"Aggregate  (cost=160706.41..160706.42 rows=1 width=32) (actual time=3355.234..3355.235 rows=1 loops=1)"
"  ->  Hash Left Join  (cost=159631.13..160581.41 rows=50001 width=8) (actual time=3324.236..3351.385 rows=50001 loops=1)"
"        Hash Cond: (pr.id = cc.product)"
"        ->  Seq Scan on product pr  (cost=0.00..819.01 rows=50001 width=4) (actual time=0.025..5.641 rows=50001 loops=1)"
"        ->  Hash  (cost=159027.97..159027.97 rows=48253 width=12) (actual time=3323.202..3323.202 rows=50000 loops=1)"
"              Buckets: 65536  Batches: 1  Memory Usage: 2661kB"
"              ->  Subquery Scan on cc  (cost=158062.91..159027.97 rows=48253 width=12) (actual time=3285.821..3308.588 rows=50000 loops=1)"
"                    ->  Finalize HashAggregate  (cost=158062.91..158545.44 rows=48253 width=12) (actual time=3285.819..3301.673 rows=50000 loops=1)"
"                          Group Key: s.product"
"                          ->  Gather  (cost=147447.25..157580.38 rows=96506 width=12) (actual time=3168.262..3214.404 rows=150000 loops=1)"
"                                Workers Planned: 2"
"                                Workers Launched: 2"
"                                ->  Partial HashAggregate  (cost=146447.25..146929.78 rows=48253 width=12) (actual time=3153.383..3168.983 rows=50000 loops=3)"
"                                      Group Key: s.product"
"                                      ->  Parallel Seq Scan on shipmentdetail s  (cost=0.00..115197.00 rows=4166700 width=8) (actual time=0.024..801.467 rows=3333334 loops=3)"
"Planning Time: 0.383 ms"
"Execution Time: 3358.020 ms"


То есть PostgreSQL не принимает никакого решения, а что сказали то и делаю.
то есть, оно в принципе есть.

Так она не join протолкнула, а значение. То есть PPD там есть, JPPD нет.
А как оно должно в вашем запросе работать, какой предикат протолкнется в подзапрос, если условие по имени продукта, а подзапрос про имена продуктов не знает?

Я в общем то в статье описал как это делают другие субд. Но по сути да, они вставляют что-то вроде lateral, как вы и написали.

Что кстати весьма забавно, что lateral PostgreSQL поддерживает, а JPPD нет.
Из области ORM, и ORM же ее и решает.

Ну как решает, пытается решить. Точнее разработчик должен это делать. Но это в следующих статьях про ORM/ERP фреймворки.
При работе с базой из приложения, в тех местах, где она может возникнуть, она легко оптимизируется до массовых вставок.

Что значит легко? Переписыванием процедуры по сути. Или дублированием что еще хуже.
В примере все параметры вводятся извне и добавляются в таблицу при вызове процедуры, поэтому непонятно, откуда там может что-то выбираться до вызова.

Там имелось ввиду что нужно вызвать эту процедуру для всех записей какой-то таблицы.
Строго говоря и хранимок в SQL нет. Это тоже процедурные решения.

Понятно, что если есть какие-то сложные рекурсивные зависимости: когда команда изменяет данные, которые читает следующая команда в процедуре, тогда да такую, штуку тяжело оптимизировать. Но очень большое количество простых процедур (без таких зависимостей) можно и нужно оптимизировать. Проверено на практике.
Ну то есть остатки будут приблизительные? Интересно что скажут пользователи на такое? Особенно, если их надо в транзакции использовать.
1 есть неявно. Когда вы цикл будете делать, там вам запрос для получения 1000 записей скорее всего понадобится (FOR row IN (SELECT ...)). Но да, базовая проблема вообще из области ORM.

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

Information

Rating
Does not participate
Works in
Registered
Activity