Comments 87
Вообще, статья интересная, но заголовку соответствует только полтора абзаца, и то, очень и очень сдержанно и неполно.
Лично передо мной сейчас дилемма: есть приложение использующее реляционную базу и она отлично подходит для текущего функционала, но в планах добавить новые фичи, которые без костылей не реализовать в *SQL, а в монго они «взлетят с пол пинка».
Кроме легкого шардинга, что?
Естественно, если сравнивать с Postgres, а не с MySQL.
P.S.: Я вообще предпочитаю связку Postgres + Redis для OLTP+, хотя последний не нравится многим (ну так путают области применения).
Мы используем JSONB-поля, всё хорошо. Индексы на них строятся, что радует безмерно.
Рекомендую не городить логику в приложении, а форкнуть ваш ORM и влить функционал JSON туда. И отдать в upstream обратно.
Польза всем, и приложение будет проще.
P.S.: да, мы тоже просто расширили ORM.
P.P.S.: Postgres для биллинга и что-то иное для контента — имеет право на жизнь, все зависит от потребностей.
Поддержка schemaless в библиотеках/фреймворках, ориентированных на Mongo. Постгресс базу тоже можно свести к schemaless, оставив в каждой таблице два поля: id и jsonb data (попутно выкинув кучу таблиц часто), но вот поддержка таких SQL типов в различных ORM оставляет желать лучшего.
:)
С другой стороны, если у меня точные данные или нужна сложная аналитика, то тут уже придется брать *SQL базу на одной машине с репликами, и надеятся, что до шардинга дело дойдет нескоро.
его имеет смысл использовать для простых CRUD-приложений
Очень сильное упрощение. Плюсы SQL заключаются прежде всего в транзакционности, в возможности атомарно для всех остальных менять одновременно несколько сущностей либо не менять ни одной, если что-то пошло не так. В случае если архитектура микросервисная, где каждый микросервис отвечает за одну сущность/агрегат (в терминах DDD), необходимость что-то атомарно менять в нескольких местах одного приложения (микросервиса) часто не возникает и выбор SQL/NoSQL часто сводится к оценке направления развития конкретного приложения (микросервиса) — будем масштабироваться в будущем горизонтально или будем усложнять логику хранения.
А я пишу на SQL больше 10 лет, преподаю его, и в том числе мне пришлось писать всякий ад на PL/SQL. И после этого использовать ORM это счастье. Логика остаётся в коде, снижается порог входа для разработчиков, и в любой момент можно перейти на любую другую РСУБД щелчком пальцев.
Но гордые знатоки однострочных запросов MySQL минуснут и этот комментарий.
Если вкратце, то никакой серебряной пули, конечно, нет, и в случае сильно сложной СУБД лучше писать запрос руками — но по факту большинство проектов укладываются в рамки использования ORM.
а потом проследить, что была создана адекватная струтура и сгенерированы оптимальные запросы.
Это как бы не сложнее чем знать все диалекты.
А уж степень глупости потерянного времени прямо таки безразмерная. Ибо сначала создаем себе трудности, а потом героически их преодолеваем.
ORM снижает требования к качеству среднестатистического разработчика — это его основной плюс и выгода для бизнеса.
Это как бы не сложнее чем знать все диалекты.
Тут на самом деле много вариантов.
Самый популярный — забить и считать, что ORM всё знает лучше.
Можно замерять время запросов с подготовленными обширными фикстурами и считать, что раз время нормальное — то всё ок.
Можно для отсмотра полученных запросов использовать тимлида. Довольно типичная для него задача, и ему обычно приходится знать все используемые диалекты.
ORM снижает требования к качеству среднестатистического разработчика — это его основной плюс и выгода для бизнеса.
Если бы технология была выгодна только для бизнеса, бизнес бы о ней никогда не узнал (кто б ему рассказал), да и до масс это бы не дошло. Всё же, хоть доля истина в этом есть, правильный ответ немного другой. Согласен в данном случае со stack overflow — это помогает писать достаточно абстрактный и портируемый код. К примеру, сейчас у меня есть pet project, который мы пишем с несколькими разработчиками. При этом у меня mariadb, а у них postgres. И почему бы и нет.
2. Так как существует как ты говоришь десятки диалектов SQL, существует десятки разных ORM.
3. Бывают заковыристые запросы, которые никак не написать на ORM, и нужно опять писать на SQL.
С точки зрения MongoDB, здесь преимущество то, что у нас гибкий JSON-формат документов. Для некоторых задач и каким-то разработчикам это удобнее, чем мучиться с добавлением колонок в SQL-базах данных. Не нужно учить SQL — для некоторых это сложно. Простые запросы реже создают проблемы. Если посмотреть на проблемы производительности, в основном они возникают, когда люди пишут сложные запросы с JOIN в кучу таблиц и GROUP BY. Если такой функциональности в системе нет, то создать сложный запрос получается сложнее.
Это уже через чур. Ощущение, что тебя разводят.
Если по теме, то считаю, что отсутствие схемы очень редко, когда действительно нужно. Чаще всего данные можно формализовать, а если так, то явная структура таблицы(документа) обязана быть. А если у нас множество таблиц со связями между ними, то реляционные БД дают слишком сильные преимущества.
А если у нас множество таблиц со связями между ними
Часто встречаюсь, что SQL и ко настолько проникли в мозги разработчиков, что таблицы и связи городят даже там, где их быть не должно в принципе, не говоря о том, где можно решить задачу другими способами не менее эффективно. Вот на днях проблемка возникла у бизнеса — есть некий документ, который оформляет пользователь системы, на печатной форме документа его ФИО стоит. Разработчики, создавшие систему изначально, не мудрствуя лукаво сделали в таблице документа поле user_id, ведь ФИО есть в таблице пользователей. Нормализация наше всё и т. п. Но не учли, что людям, особенно девушкам, свойственно иногда менять ФИО, а на печатных формах ФИО пользователя должно быть на момент, грубо говоря, создания документа.
отсутствие схемы очень редко, когда действительно нужно
Полное отсутствие, да. Но очень часто нужно либо часто менять схему, либо часто добавлять новую, но очень близкую к имеющейся. Часто это вырождается либо в одну таблицу с кучей (реально куча, даж не десятки) nullable полей, либо в сложные схемы типа одна главная талица, десяток связанных 1:0..1 и имя таблицы с которой связь в поле главной, что приводит к чему-то типа:
SELECT * FROM main_table
LEFT JOIN table_1 ON main_table.id = table_1.id AND main_table.type = 'table_1'
-- ...
LEFT JOIN table_10 ON main_table.id = table_10.id AND main_table.type = 'table_10';
А почему просто не мигрировать на новую схему?
Но не учли, что людям, особенно девушкам, свойственно иногда менять ФИО, а на печатных формах ФИО пользователя должно быть на момент, грубо говоря, создания документа.
К реляционным базам это имеет никакое отношение.
В вашем конкретном случае документы должны были иметь два поля user_id, user_fullname, например.
Это если нигде более смена фамилии не является критичной.
В противном случае вам придётся вынести список фамилий и имён пользователя выносить в отдельную сущность и хранить в отдельной таблице user_id, firstname, lastname, date_create…
Но интересен другой вопрос.
MongoDB и прочие schema-less решения каким образом внезапно спасли бы вас в описанном вами случае?
Имеет, такие схемы сейчас по умолчанию делают очень многие, впитав шесть (или сколько их там) форм нормализации реляционных баз данных чуть ли не с молоком матери, не вникая ни то, что в бизнес-процессы, но и игнорируя свои обычные бытовые знания, о том, что ФИО может меняться, паспорта теряться и т. п. В терминах теории "лучшие практики" их приучили любой домен объявлять отдельным отношением, "заставляя" не делать его набором атрибутов других отношений, а использовать соединения между отношениями.
Внезапно, да. По умолчанию в них бы внесли значение user_fullname, если необходимость какой-то нормализации и дедупликации явно не задана в ТЗ, просто чтобы не париться. "Реляционщики" чтобы не париться нормализуют по умолчанию, где надо и где не надо, чтобы обеспечить дедупликацию ценой использования JOIN-ов, а "документщики" чтобы не париться денормализуют где надо и где не надо, чтобы избежать запросов подобных JOIN-ам.
Внезапно, да. По умолчанию в них бы внесли значение user_fullname
Ну таки в ловушку попались.
Ведь для случаев, когда критична смена фамилии вам же нужно знать не только фамилию подписавшего, но и однозначно идентифицировать его по этой фамилии.
А из вашего рассказа получается что после смены фамилии документы теряют связь с пользователем, но продолжают хранить информацию о его бывшей фамилии…
То есть в документе зафиксирован Вася Пупкин, а вот самих Пупкиных в вашей БД при этом может не быть вовсе теперь.
Так что, как вы справедливо указали, проблема именно в
не вникая ни то, что в бизнес-процессы
И schema-less в данном случае просто создают другой тип ошибки неконсистентности данных
В чём разница межды мышлением «реляционщиков» и «документальщиков». Первые по умолчанию стараются оставить только id, вынеся всё остальное в отдельные таблицы, рассчитывая на джойны, а вторые стараются кроме id внести всё, что может понадобиться чтобы избежать джойнов.
Но вообще, для документарных баз вполне нормальна ситуация, когда наличие ссылки в одних документах на другой не препятствует удалению последнего. Проще говоря, даже если удалим пользователя из коллекции, то не будет ошибкой пустой результат при поиске в коллекции пользователей, ведь все нужные для заказа данные пользователя у нас сохранены непосредственно в документе заказа. Собственно user_id там на всякий случай обычно. Реляционные же «заставляют» нас делать либо каскадное удаление, либо вводить какой-то атрибут «уволен» и учитывать его в одних процессах, типа на кого назначить заказ и не учитывать в других типа печати документов.
А обнулить user_id реляционные БД не дают возможности?
Дают, но зачем его обнулять? Главное, что РСУБД обычно дают не делать ограничения на значения типа reference, хотя их практически все по умолчанию делают, даже если в ТЗ отсутствует требование на соблюдение ссылочной целостности в базе и вообще база не упоминается.
Вообще есть две красные тряпки для "реляционщиков":
- нарушение ссылочной целостности
- дупликация данных
Они, грубо говоря, приходят в двойное бешенство если видят в таблице order 1000 записей user_id=1, user_name = "Иванов Иван Иванович" и при этом в таблице user нет записи c id =1 и вообще в таблице order нет ссылочных ограничений на поле user_id. Разработчика такой схемы они автоматически объявляют бэдлокодером, раз он дублирует данные и не поставил ограничение. Даже не поинтересуются были ли в постановке задачи соответствующие требования.
Вы уверены, что эти красные тряпки существуют не только у "реляционщиков" у Вас в голове? Как и сами "реляционщики"?
Я уверен, что они существуют у "реляционщиков", как и в том, что они сами существуют. Прежде всего это те, кто нереляционные базы не использовал ("нет джойнов и ссылочной целостности? даже время на посмотреть тратить нет смысла"), а при знакомстве с проектом в первую очередь лезут в базу данных, а потом долго возмущаются, что референсы там не прописаны, и вообще таблицы не маппятся на объектную модель 1:1, а то есть таблицы которые в объектной модели не представлены, а то есть объекты которые в базе хранятся, но таблицы для них нет.
Реляционные же «заставляют» нас делать либо каскадное удаление, либо вводить какой-то атрибут «уволен»
Считаете неправильным хранить значимую историю изменений?
Считаю правильным хранить значимую историю изменений. Так же считаю, что реляционная модель для этого подходит плохо, поскольку не оперирует упорядоченными множествами. Главное преимущество реляционного моделирования — не дублируем данные, потом мучаясь с их синхронизацией, а изменяем в одном месте и это изменение распространяется на все выборки. Для фиксации же исторических значений это не походит, суть такой фиксации — зафиксировать значение на какой-то момент времени и держать его в независимости от следующих изменений.
Правильное моделирование сущностей типа заказа заключается в копировании всех значимых для заказа данных из текущего состояния сущностей типа клиент и товар, собственно даже идентификатор этих сущностей в заказе особо не нужен, кроме как для аналитических целей типа посчитать сколько заказов сделал конкретный клиент или сколько конкретного товара заказано. Но "лучшие практики" реляционного моделирования "заставляют" нас из заказа только на сущности ссылаться, дублируя только те данные из сущностей, которые явно в ТЗ указаны как подлежащие дублированию. Либо вводить дополнительные сущности типа "состояние сущности такой-то на такой-то момент" и ссылаться на них явно через идентификатор, либо через составной ключ (часто неявный, без ограничений reference) типа ссылки на основную сущность и дату на которую нужно фиксировать изменение.
Базой ее является то, что данные представляются в формате связей.
Вас кто-то знатно потроллил на тему нормальных моделей.
Так вот — нормальные формы как и любые другие абстракции следует применять там, где это уместно. Непосрественно же реляционная модель на это не накладывает ограничений.
Правильное моделирование сущностей типа заказа заключается
Неа, не заключается.
Ибо зависит от анализа предметной области. Вы же не тупо 1 в 1 копируете бизнеспроцессы, которые родились еще во времена бумажного документооборота и никакой другой формы кроме «копирования значимых данных» не предусматривали.
Если кто и троллил, то код, с которым сталкиваюсь ежедневно, в котором связи (причем с контролем ссылочной целостности) лепятся просто по умолчанию.
Именно, что тупо копирую. Моя задача как разработчика автоматизировать имеющиеся бизнес-процессы, зачастую заданные даже не бизнесом, а государством.
Именно, что тупо копирую. Моя задача как разработчика автоматизировать имеющиеся бизнес-процессы, зачастую заданные даже не бизнесом, а государством.
Фиговые у вас аналитики, раз ставят именно такие задачи.
Например, тот же НБУ еще в 1999 году издавал соответствующие постановления об электронном документообороте. И там была формализация результатов, а не процесса.
2.1.1. Вимоги до первинних облікових документів
Підставою для бухгалтерського обліку операцій банку є
первинні документи, які фіксують факти здійснення цих операцій.
Первинні документи повинні бути складені під час здійснення
операції, а якщо це неможливо — безпосередньо після її закінчення
та можуть складатися у паперовій формі та/або у вигляді
електронних записів (у формі, яка доступна для читання та виключає
можливість внесення будь-яких змін). У разі складання їх у вигляді
електронних записів при потребі повинно бути забезпечене отримання
інформації на паперовому носії.
Первинні документи як у паперовій формі, так і у вигляді
електронних записів (непаперовій формі) повинні мати такі
обов'язкові реквізити:
— назву документа (форми);
— дату складання документа; { Абзац п'ятий підпункту 2.1.1
пункту 2.1 із змінами, внесеними згідно з Постановою Національного
банку N 283 ( z0675-01 ) від 18.07.2001 }
— назву підприємства (банку), від імені якого складений
документ;
— місце складання документа;
— назву отримувача коштів;
— зміст операції (підстави для її здійснення) та одиницю її
виміру; { Абзац дев'ятий підпункту 2.1.1 пункту 2.1 із змінами,
внесеними згідно з Постановою Національного банку N 203
( z0926-12 ) від 23.05.2012 }
и т.д. и т.п.
Если кто и троллил, то код, с которым сталкиваюсь ежедневно, в котором связи (причем с контролем ссылочной целостности) лепятся просто по умолчанию.
А это скорее от тяги попробовать все новое. И раз есть — надо применять…
Я в одном месте встретил каскадное удаление первичных документов после удаления акции по которой они могли создаваться.
В результате посетитель приходил, ему приходила смс, а вот заявки о нем в БД не было от слова вовсе…
Кмк, здесь проблема сложнее, чем кажется на первый взгляд.
При нормализации избыточные данные обычно заменяются на связи между сущностями. Под сущностью, как правило, подразумевается первичный ключ плюс текущее состояние. Здесь имеет значение не текущее состояние, а состояние на определенный момент времени. Это не денормализация, это связь с конкретным состоянием сущности.
То есть технически нормализация была сделана не совсем правильно, не были учтены некоторые связи, из-за чего произошла потеря информации.
Надо различать изменение текущего состояния и создание нового. Изменение распространяется по всем связям и влияет на все время занесения данных, создание влияет только на будущие данные.
В общем случае надо делать ссылки на состояние объекта. То есть объект это просто primary key + state id. А state хранится отдельно. Там, где всегда требуется текущее состояние, используется object_id, там где конкретное object_state_id.
Это конечно теоретические рассуждения.
А, ну вот, как раз все сходится. Сущность это primary_key + state_id, значит state_id это объект-значение. Объекты-значения составляют множество, элементам одного множества можно сопоставить элементы другого множества. Например целые числа. То есть идентификатор состояния это не первичный ключ, а просто короткое обозначение значения.
SELECT
order.*,
(
SELECT full_name
FROM history
WHERE history.user_id = order.user_id AND history.date <= order.date
ORDER BY history.date DESC
LIMIT 1
)
FROM order
или ещё какую его вариацию, пускай
SELECT
order.*,
history.full_name
FROM order
INNER JOIN history ON history.user_id = order.user_id AND order.date BETWEEN history.valid_from AND CASE WHEN history.valid_to IS NOT NULL THEN history.valid_to ELSE current_timestamp END
Не говоря о том, насколько если усложнит логику приложения, если задачи отслеживания истории как таковой не стоит.
Вот на днях проблемка возникла у бизнеса — есть некий документ, который оформляет пользователь системы, на печатной форме документа его ФИО стоит. Разработчики, создавшие систему изначально, не мудрствуя лукаво сделали в таблице документа поле user_id, ведь ФИО есть в таблице пользователей. Нормализация наше всё и т. п. Но не учли, что людям, особенно девушкам, свойственно иногда менять ФИО, а на печатных формах ФИО пользователя должно быть на момент, грубо говоря, создания документа.
Тут проблема не в SQL, а в ДНК того, кто проектировал workflow.
Если у объекта со временем может меняться какой-то атрибут (ФИО юзера в данном случае, а еще могут меняться паспортные данные, размер достоинств и т.д.), то надо завести отдельную таблицу и там хранить историю этих изменений. В случае очаговости таких явлений (не все юзеры женщины, не все женщины выходят замуж «после того как», не все вышедшие замуж меняют фамилию и т.д.) это будет намного выгоднее с точки зрения использования ресурсов, чем во всех документах жестко забивать в шапку этот реквизит. Особая прелесть вылезет когда выяснится, что атрибут объекта изменен «задним числом» или при изменении атрибута допущена ошибка. В случае таблицы с историей изменений редактируется только запись в ней, и не нужно бегать как умалишенному по всей БД в поисках этих ФИО, проверке их на необходимость исправления и собственно исправлении — обязательно что-то будет упущено.
И да: для особо критичных случаев можно по-прежнему жестко фиксировать в документе текущие значения атрибута. Но это должно быть исключением, а не правилом.
Ага, стандарт SQL-92 не в полном объёме. Очень даже захочется!
"достаточно простой" — это правда, если не вешать на сторону СУБД кучу логики в хранимках и т. п. Простая такая миграция прежде всего в смысле того, что не придётся кардинально менять принципы хранения данных.
Как минимум, чтобы не страдали админы. :) По крайней мере очень широко распространено мнение, что мускуль проще в администрировании, чем постгри, админам с ним меньше возиться, не нужно перезагружать на каждый чих и вообще, с чем я субъективно после 15+ лет работы с мускулем и года с постгри согласен — писать почти без разницы под что, у каждой из систем есть свои нюансы, а вот админить постгри сложнее. А программиста БД чаще всего на проекте и нет, его роль выполняют в лучшем случае обычные программисты, а то и вообще ORM.
Для использования с ORM возможностей достаточно.
От количества записей в таблице ORM не зависит.
На моей практике ORM буксуют обычно в двух случаях:
- через операционную модель пытаются получать аналитику
- грубое моделирование предметной области в ФП стиле, типа "все исходные данные есть, формулы есть, остальное посчитаем", забывая, например, что результат расчёта сегодня — это исходные данные для завтрашнего расчёта, причём они не должны измениться, даже если исходные данные изменились.
я для себя ответ на вопрос из заголовка сформулировал так:
- если отношения между объектами представляют собой дерево, то документно-ориентированные БД — отличный выбор. Например, мы пишем "Кинопоиск", где нам нужно хранить всякую разную информацию о фильмах:
{ "title": "The Matrix", "year": 1999, "cast": [ {"name": "Keanu Reeves", ... } ] }
или сериалах:
{ "title" : "Big Bang Theory", "series" : [ { "number" : 1, "actors" : [ { "name" : "Kelly Cuoco" } ] } ] }
здесь каждый объекты однозначно связаны отношениями "родитель-потомок", и большую часть информации (список актеров сериала, список серий, в которых снимался актер), в MongoDB можно легко получить одним запросом с помощью aggregation framework.
- но если в графе отношений между сущностями есть циклы, т.е. один объект может быть одновременно связан с несколькими, реляционные БД — более мудрый выбор.
Например, мы пишем Хабрахабр, где у нас есть посты и комментарии. У постов есть авторы, у комментариев так же есть авторы, то есть авторы относятся одновременно и к комментариям, и к постам.
Если мы используем MongoDB, придётся делать или так:
Пост:
{ "title": "Заголовок Поста", "author": { "name": "username", } }
или пойти на ухищрения со ссылками, о которых упоминал в своём докладе Пётр:
{ "title": "Заголовок Поста", "author_id": ObjectId("58b1422426353d52d75f5cc9") }
Оба варианта заставят нас реализовывать логику с удалением, сохранением консистетности ключей на уровне приложения. Зачем это делать, когда реляционные БД умеют это делать за нас, и такая структура данных для них более естественна — непонятно. Мне кажется, что в этом случае выбирая документноориентированную БД, мы гораздо больше проигрываем, чем выигрываем
> вместо простого знака «>». Не очень читаемо, на мой взгляд.
Странно, что вы пишете амперсанд — и в коде и в тексте. В монго операторы со знака доллара начинаются.
А так, логика формата операторов вполне понятна. Чтобы не спутать оператор с названием поля, он начинается со специального знака. Символ
>
, кстати, вполне может быть названием поля (так что его нельзя использовать в качестве оператора) — в названии поля можно использовать любые символы из UTF-8 (кроме точки в любой части строки или знака доллара в начале строки). И это вполне читаемо, потому что все операторы созданы по одному и тому же принципу: знак доллара + текст, по которому часто (не всегда, но часто) и без документации понятно, что происходит.&gt
. Этот код на автопилоте обычно не вводится, потому что он требует дополнительных осмысленных телодвижений. То есть, в этот момент нужно реально верить, что команды в mongo начинаются с амперсанда, чтобы осознанно это вставлять в текст. :)MongoDB позволяет попробовать иерархическую БД, ведь активно их использовали в 60-х.
Некорректно противопоставлять SQL и CRUD: CRUD — это парадигма, а не конкретный красивенький синтаксис
Предпочту MongoDB Postgres-у any day с административной и интеграционной точки зрения.
Postgres шагает в нужную сторону, но пока там не будет автоматического надёжного failover и поддержки streaming replication хотя бы с меньшей версии к большей, я так и буду плакать, когда нужно обновить версию или перезагрузить подлежащую железку. Существующие же решения для организации кластера требуют тонкой настройки, иногда разваливаются и не совсем прозрачны для приложения.
Разумеется это всё про [near] zero-downtime, если этого не требуется, то как БД Postgres лучше почти со всех сторон.
А, ну и .NET-драйвер для Postgres просто ужасен (в плане производительности и дизайна обработки ошибок), правда.
Заодно напишу про денормализацию в рамках MongoDB. Большинство примеров и туториалов фокусируются на неизменяемых данных относительно небольшого размера. В реальности, если поместить даже несколько сотен комментариев к посту в один документ, "денормализовав" туда никнеймы пользователей и прочие элементы, то вас ждёт жестокое разочарование, потому что под нагрузкой это будет безбожно тормозить при материализации изменений, потому что MongoDB оперирует документами, а не полями (особенно грустно с массивами).
Основой же бонус, который я вижу, это отказ от join и логики на стороне БД. Есть исключения, конечно, но разделение read models и write model обычно полезнее, чем попытка сделать вид, что одни и те же данные используются всеми клиентами одинаково. В некоторых БД есть materialized views, но и это может быть не самым оптимальным решением при нетривиальной фильтрации.
MySQL и MongoDB — когда и что лучше использовать