Pull to refresh

Comments 47

Главный минус такого решения — сложность конкурентных изменений.
Значительно больше. При EAV меняется строка в таблице, при XML — надо целиком переписывать документ. Таким образом, два разных запроса могут поменять при EAV без проблем конкурентно, а при XML — один переписывает документ целиком, потом второй.
Согласен.
Но многие приложения, которые я видел, в любом случае выполняли UPDATE всей строчки, даже если данные во ходной форме изменились только в одном поле, или вообще не изменились. Это конечно не правильно.
Теоретически и xml-документ можно обновлять только частично, новыми данными (например контроль на триггерах и т.д.).
обновлять частично можно, вынеся read-update-save цикл на сервер, но это не меняет факта, того что это будут только последовательные, а не конкурентные обновления.
А в чем сложность то?
При изменении объекта необходимо следить за целостностью целого объекта. Поэтому хоть ты изменяешь одно поле, хоть весь объект целиком, механизму конкурентных изменений должно быть все-равно.
Допустим, у нас optimistic concurrency. В обоих случаях это будет ровно одна проверка одного поля объекта. И, конечно, это поле нужно в XML варианте, хранить не в самом XML (чтобы его чтение было дешево).

Или имелось ввиду, что при XML потребуется больший объем трафика при небольших изменениях?

Поскольку первые 4 абзаца у вас состоят из ИМХО и эмоций, разрешите спростить так же:
«Ну почему из трёх вариантов EAV, NoSQL, XML. Вы выбрали самый неподходящий и отстойный. Да ещё и пример привели который решается проще на SQL.»
Эмоции присутствуют, не скрываю, праздники все таки…
На sql проще, само собой, но только когда заранее известна структура данных и заранее можно определить и продумать запросы к ней. Я же описывал ситуацию обратную, когда структура заранее не определена и периодически изменяется.
В выборе архитектуры хранения очень важны детали. Собственно поэтому в РСУБД применяются такие противоестевтвенные для неё вещи как например денормализация и хинты. А вы не привели никаких подробностей про динамическую часть схемы. И все «минусы» более вменяемых решений выглядят в такой ситуации не просто надуманными, а фиктивными.

«EAV. Вызывает у меня стойкую неприязнь, да и сказано и написано об этом было очень много всего негативного (Кайт, Фаулер, Карвин, Горман). Главный минус в том, что при написании запросов приходится оперировать уже не реальными сущностями («Сотрудник», «Дом», «Клиент», то для чего и предназначен SQL), а объектами, орагнизованными на более низком уровне (извините за сумбур). Поэтому это был самый не желательный вариант.»

Про хранение в XML всех полей записи вы найдёте не меньше негатива.

"— Бедный язык запросов (ИМХО) + отсутствие джойнов;"

Джойны там не всегда нужны. А язык запросов как правило там не на стороне сервера а на стороне приложения (хоть и выполняются они и на сервере тоже)

"— Отсутствие схем (хорошая статья недавно была на эту тему "

Учитывая динамическую природу у гипотетического примера — её необходимость в классическом смысле под сомнением.

"— Отсутствие встроенной поддержки ссылочной целостности;"

Согласен, что это массово не решённая проблема.

"— Отсутствие прибамбасов в виде хранимых процедур/функций, триггеров, представлений и многого другого."

Вы сильно отстали от жизни, это есть, но опять же, не везде.

"— В моем приложении помимо данных с гибкой(изменяемой) структурой также необходимо хранить обычные статические данные — таблица пользователей, посещений, сотрудников и т.д. Работать с которыми (опять же имхо) гораздо проще и (самое главное) надежнее в обычной реляционной базе (та же самая ссылочная целостность и пр.)."

И без деталей примера вообще не понятно можно было ли обойтись одним SQL.

Согласен со всеми пунктами. Однако целью моей статьи было показать один из возможных вариантов решения популярной проблемы, саму идею, и небольшой пример использования. Вопрос об использовании данного способа для какой либо конкретной задачи остается открытым, никакой пропаганды нет. Однако для своей задачи я (пока) остановился именно на нем. Описание ее (задачи) целью статьи не являлось, но, если это будет интересно, могу описать ее и причины выбора данного подхода.
О решаемой задаче в кратце.
Сущности (за исключением системных) создаются полностью пользователем (через пользовательский интерфейс), т.е. ядро системы ничего не знает (и не может знать) о них. Далее пользователь должен иметь возможность работать со своими сущностями, выбирать данные из них (выборки могут быть любой сложности, любой степени вложенности и т.д.). Для этого используется QBE, который автоматически преобразуется к обычному sql-запросу, так же остается возможность (для продвинутых пользователей) составлять запросы самостоятельно (для сложных, нетривиальных выборок) на стандартном, хорошо известном многим языке SQL.
При этом пользователь в любом случае (даже при ручном написании запросов) работает со своими логическими объектами, тонкости технической реализации (насколько это возможно) остаются для него скрытыми (чего не скажешь о EAV).
что мешает при этом создать tablespace, внутри которого создавать просто таблицы?
Создание таблицы в РСУБД это дорогое действие, и совершать его при каждом неосторожном действии пользователя, по любому поводу — думаю не самое лучшее решение (хотя такой вариант я тоже рассматривал).
Так же при малейшем изменении структуры таблицы(таблиц) придется изменять все пользовательские таблицы, потеряется необходимая гибкость.
а чем плох EAV?

Простая реляционная структура типа goods<-parameters->categories. Запросы вполне наглядные, будут выглядеть примерно так:

select price.goods_id
	from parameters price
	join categories priceCategory 
		on priceCategory.id=price.category_id 
		and priceCategory.name="Цена" 
		and price.numericValue>100.0 and price.numericValue<200.0
	join parameters color
	join categories colorCategory 
		on colorCategory.id=color.category_id 
		and colorCategory.name="Цвет" 
		and (color.stringValue="Поносный" or color.stringValue="Жёлтый")


т.е. выбрать иды товаров с ценой от 100 до 200 и цветом «Поносный» либо «Жёлтый». Компактные запросы вполне поддающиеся оптимизации и разграничению прав на элементы справочника параметров. И просто и работает быстро.
Проблемы начнутся, когда нужно будет организовать поиск не по всем товарам, а только по конкретным, например среди всех чайников и самоваров и когда сложность запроса немного увеличится. Тогда в ход пойдут айдишники (т.к. в боле менее сложный запрос достаточно накладно все время приджойнивать таблицы с названиями классов и типами параметров (в Вашем случае categories)), а для удобства можно будет держать в голове, что 493832 — это силикатный кипич, а 2345638 — детское питание.
Так же такую структуру сложно проиндексировать, частенько приходится делать мат представления для того или иного конкретного участка (в тех базах где они есть конечно), но от этого беспорядок только увеличивается.
нормально всё будет. Добавится ещё условие и всё

select price.goods_id
	from parameters price
	join categories priceCategory 
		on priceCategory.id=price.category_id 
		and priceCategory.name="Цена" 
		and price.numericValue>100.0 and price.numericValue<200.0
	join parameters color
	join categories colorCategory 
		on colorCategory.id=color.category_id 
		and colorCategory.name="Цвет" 
		and (color.stringValue="Поносный" or color.stringValue="Жёлтый")
	join parameters kind
	join categories kindCategory 
		on kindCategory.id=kind.category_id 
		and kindCategory.name="Изделие" 
		and (kind.stringValue="Чайник" or kind.stringValue="Самовар")
Запросы EAV имеют свойства увеличиваться от сложности задачи гораздо быстрее, чем обычные реляционные.

Вот аналог с применением xml:
select *
  from objects C
 where ((xpath('/Изделие/Наименование/text()', O.body))[1]::text::int in ('Чайник', 'Самовар')
   and ((xpath('/Изделие/Цвет/text()', O.body))[1]::text::int in ('Желтый', 'Поносный')
   and ((xpath('/Изделие/Цена/text()', O.body))[1]::text::int between 100.0 and 200.0;
ну и в чём разница? Только в том что SQL-сервер выполнит запрос мгновенно а все известные мне XML-движки дадут огромный провал в производительности.
16 строчек против 5 плюс абсолютная нечитаемость, и это на элементарном запросе.
К EAV невозможно применить нормальной индексации, при среднем и большом количестве записей «мгновенно» не получится.
нормально в постгре eav оптимизируется и индексируется за счет битмапов.
А разве использовать битмапы на редактируемых данных не преступление? :)
битмаповые карты индексов позволяют использовать индекс по двум независимым колонкам для выборок OR/AND и вместо совместного индекса
кстати очень переспективная штукенция.
но не всё прозрачно с индексами.
уже пару недель эксперименты ставлю.
В-нулевых, для EAV можно и нужно написать хороший ORM, который будет вас абстрагировать от низкоуровневых объектов.
Во-первых, для производительности нужно грамотно настроить индексы и саму db а так же можно делать view's (в оракле) или аналоги в других db.
Во-вторых, три таблицы это самый минимум, на самом деле для удобства использования их нужно иметь несколько десятков.
В-третьих, для очень сложной enterprise-системы вы ничего гибче не придумаете. Не зря, например Magento, на EAV работает.

Моя компания делает продукт, для которого данные хранятся в EAV, спрашивайте вопросы, постараюсь ответить по мере знаний (я не пишу ядро и очень многих тонкостей не знаю) и соблюдения NDA.
а есть что-то нетривиальное? расскажите.
Так как ядром я не занимаюсь то навскидку ничего хитрого вспомнить не могу. Разве что проблему с ANSI джоинами в оракле, у которых есть ограничение то ли в 1000 то ли около того, в результате чего на больших запросах оракл тормозил и падал с ошибкой, пока все не переписали на нативные джоины.
В-нулевых, для EAV можно и нужно написать хороший ORM, который будет вас абстрагировать от низкоуровневых объектов.

EAV нужен там, где не достаточно ORM: хранение неопределенного заранее (или постоянно изменяющегося) набора сущностей и аттрибутов.
Если же вы можете заранее построить классы для Ваших сущностей (на основе ORM), то почему бы не использовать обычную реляционку.

Во-первых, для производительности нужно грамотно настроить индексы и саму db а так же можно делать view's (в оракле) или аналоги в других db.

Хотел бы посмотреть как строится индексы для EAV-модели.
Насчет вьюх (и мат вьюх) согласен, т.к. это остается единственным способом хоть как то управлять производительностью и читаемостью запросов. Получется, что вместо таблиц приходится создавать представления.

Не зря, например Magento, на EAV работает.

И поэтому, начиная со 2-й версии, от него избавляются:
dimitrigatowski.com/2011/06/19/magento-2-preview/
Я всем рекомендую расширение hstore (http://blog.evtuhovich.ru/blog/2012/01/23/hstore/). За счет его введения устраняется один из основных недостатков реляционных баз данных.

Автору. Не морочьте голову себе и людям.
hstore интересное решение, но (как и любое другое) не панацея.
Не так давно решали аналогичную задачу, с некоторыми отличиями — динамическая схема нужна только для части колонок, и важна производительность на довольно сложных выборках (много критериев фильтрации + сортировка).

EAV был отброшен как непрактичный (и вероятно ещё и очень медленный). XML и hstore как слишком медленные и требующие много памяти для хранения. Остановились на JSON, накидали функции для его поддержки на perl, а позднее на c.

Если интересно могу написать пост об этом.
+1 за написание поста.
Интересно уточнить чем ваше решение отличается от той же mongodb?
Вопрос не мне (так что извиняюсь), но как минимум, поддержка транзакций и нормальных sql-запросов.
Основная причина — для наших задач хороши реляционные бд: транзакции, ссылки на другие таблицы, нецелесообразность переноса всех остальных данных приложения, готовая, заточенная инфраструктура — ORM, кеширование, репликация и партицирование. Выборки по большому количеству непредсказуемых критериев, т.е. то, для чего старые добрые реляционные дб хороши.

Так что добавлять новую технологию в свой стек особого резона не было.
Прошу прощения, невнимательно прочитал вопрос и ответил на «почему вы не использовали MongoDB».

А отличается наше решение тем, что мы продолжаем использовать PostgreSQL со всеми его фишками и наработками со стороны приложения, и тем, что не надо ничего мигрировать, кроме нескольких таблиц, для которых гибкие схемы и нужны
спасибо за ответ. я также +1 за написание Вами поста.
а в 9.2 вроде json штатно прикрутили. даже с индексами
хм. надо покопать. давненько хотелось что то такое абстрактное для логов, например.
— Бедный язык запросов (ИМХО) + отсутствие джойнов;

язык запросов не бедный, просто нужно в нем разобраться, относительно джоинов:
docs.mongodb.org/manual/applications/database-references/
группировка:
docs.mongodb.org/manual/reference/command/group/

согласен, все не так просто как хотелось бы, но это есть!
Да, я в курсе, и про эту штуку тоже, но ведь это все равно «был не Нескафе».
не Нескафе, не Чибо и не Якобс, а Несквик, такой же веселый и свободный, выражаясь аллегорично. Просто большинство тех, кто работал с реляционками пытается применить тот же подход к Mongo, но она принципиально другая, дает больше свободы, которой нужно воспользоваться с умом, в начале от нее кружится голова, а потом понимаешь, что зашел далеко, все плохо, и нет, она тебе не подходит, но дело в том, что просто нужно научиться сначала планировать базу а потом ее реализовывать. Ничто не мешает написать обертку, которая будет проверять целостность данных. Как писалось в другой недавней статье, все мы мыслим схемами, в реляционных субд база хранит эту схему за нас, а в не реляционных ее нужно хранить в голове, ну или реализовать самостоятельно алгоритм ее поддержания.
Обертку — можно (это я кстати и пытался сделать, когда начинал с Mongo), но во первых это придется делать самому (и работать это будет наверняка медленне, а в реляционке уже все в коробке), а во вторых к такой базе не подойдешь ни слева ни справа, только из конкретного приложения, малейший порыв даже самого легкого ветра со стороны — и что то пошло не так.
Но это конечно от задачи зависит.

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

Про обертку спорно, я лично пишу под php, и пишу так, чтобы ее можно было использовать независимо от конкретного приложения, чтобы если есть монго и обертка использовать их вместе можно было из любого скрипта.
Sign up to leave a comment.

Articles