Pull to refresh

Comments 36

UFO just landed and posted this here
UFO just landed and posted this here
я тоже сначала подумал о наследовании.
в коде наверное наследование будет неплохим вариантом, однако в структуре базы данных это будет одна таблица рейтинга — на каждую сущность.

а автор предлагает использовать одну таблицу для всех сущностей, но указывать в этой таблице тип сущности для которой делается рейтинг.

однако на месте автора я бы привел цифры по нагрузочному тестированию такого подхода к рейтингу.

возможно быстрее будет выбирать данные из разных мелких таблиц чем из одной большой.
Всем понятно что мой вариант будет работать медленнее, но кэширование может исправить сютуацию.
кэширование всегда исправляет ситуацию… но когда (если) столкнетесь с реальной нагрузкой — вы ещё и шардить начнёте рейтинги одной сущности по разным таблицам… потому что лучше 5 таблиц с индексом по одному полю и по 20 млн. записей, чем 1 таблица с индексом по двум полям и на 100 млн.
труд можно СИИИИЛЬНО облегчить не плодя лишних сущностей
И снова бы злесь Капитан Боян!@nike-17
ru.wikipedia.org/wiki/%D0%91%D1%80%D0%B8%D1%82%D0%B2%D0%B0_%D0%9E%D0%BA%D0%BA%D0%B0%D0%BC%D0%B0
Кстати таблицу Post и Comment волне можно и соединить (в одном из внутрикорпоротивных движков блого-форума я так и сделал).
единая таблица:
Post:
id
parent_id
user_id


если parent_id — 0/null — то это пост/начало форума, инача коммент, ответ.
Я не говорю что это правильно и так нужно делать, просто ради прикола в одном небольшом проекте я такое забацал.
Ну часто бывает так, что у поста и комментария разные параметры могут быть, а так да вполне интересный вариант.
UFO just landed and posted this here
Неужели, однотабличное наследование? =)
UFO just landed and posted this here
Информацию о рейтинге я бы однозначно закешировал в соответствующем поле у сущности. А делать выборку из таблицы Rating следовало бы только при голосовании (чтобы исключить повторные)
Да я так и делаю тем более что дописать кэширование в этот код достаточно просто.
Походит на ContentTypes-фреймворк в джанге. Такие связи там называются generic relations. Только там все это зашло далеко вперед, с достаточно полной поддержкой со стороны ORM. Очень широко применяется при написании подключаемых приложений.
Gibbzy пишет: «Как известно в работе вёб программиста часто нужно делать что то достаточно быстро, для этого придумали множество всяческих инструментов ООП, фреймворки и много всего, но как не порождать монстров, а делать всё быстро и качественно?»

Просто знать и использовать правильные фреймворки. Хорошо разобраться в фреймворке сложнее, чем написать велосипед. Но, как мне кажется, полезнее.

И в джанге, и в рельсах (как пишет LightAlloy) это все давно реализовано и широко применяется (на рельсах не писал, но, думаю, что там тоже), с разными удобными плюшками и много-раз-наступленными граблями.

P.S. Этот конкретный велосипед считаю полезным, ход мыслей — верным.
в целом решение имеет место быть, но:
1) когда сервис с высокой активностью, общая таблица будет излишне переполнена.
2) раз уж зашел разговор про Zend Framework… Не место этим методам в этом классе — мы не рейтингу рейтинг выставляем и иже с ним.
Следовательно нужены:
— абстрактный класс от которого будут наследоваться классы конкретных таблиц
abstract class VotingTable  extends Zend_Db_Table_Abstract {
    protected $_rowClass = 'Voting';

  /**
  * @return Zend_Db_Select
  */
  abstract function getTableType();
}

— каждый класс наследующий VotingTable должен содержать определение зависимостей через $_dependentTables и реализовывать специфичный select (определение метода getTableType) в которым бы выборка производилась с учетом типа таблицы.
— Класс Rating должен содержать правила взаимосвязей с таблицами по которым ведется голосование ($_referenceMap)
— класс Voting extends Zend_Db_Table_Row_Abstract, содержащий методы работы с рейтингом с немного иной сигнатурой, которые в свою очередь будут проксировать вызов магических методов ($row->findTableClassViaIntersectionTableClassByRule1) родного механизма выборки многие ко многим [http://framework.zend.com/manual/en/zend.db.table.relationships.html#zend.db.table.relationships.fetching.many-to-many] и и создавать новые инстанции Rating (методом setRating).
class Voting extends Zend_Db_Table_Row_Abstract {
  protected $_tableType;

  public function init() {
     $this->_tableType = $this->getTable()->getTableType();
  }

  public function setRating($user, $amount)  { /* ... */ }
  public function getRating() { /* ... */ }
  public function isVoted($user_id) { /* ... */ }

}


Как-то так…
«1) когда сервис с высокой активностью, общая таблица будет излишне переполнена.»
в чём кардинальная разница, 3 таблицы с N записями суммарно или 1 таблица с N записями?
UFO just landed and posted this here
UFO just landed and posted this here
а вы сможете эту разницу измерить? вы уверены, что вы сейчас не экономите на списках?
озвучьте, пожалуйста, абсолютную разницу в секундах между этими 2 вариантами?

пусть, к примеру, в каждой из таблиц будет миллионов по 50 записей.
заполните и выложите результаты. потом сделайте вывод о целесообразности.
Ну и ещё дополнительное условие поиска WHERE field_type=? тоже немного замедляет…
вы можете это «немного» измерить?
уточняю: не могли бы вы вместо аморфного и жутко сферического «немного» указать цифры, в секундах, насколько 1 таблица будет медленнее?
Прикаждом изменении таблицы придется также индексы пересчитывать — в маленькой таблице быстрее.
При большом объеме вставок будет лочиться вся таблица и больше запросов будут висеть в оидании разлока — несколько таблиц выгоднее.
любой join с бОльшей таблице будет медленнее чем с маленькой.

В общем — с точки зрения удобства программирования — здорово, с точки зрения производительности — нет, imo.
уже дважды попросил товарищей сверху измерить эти сферические «быстрее» и «медленнее».
попрошу и вас. можете показать, насколько это будет «медленнее»? будет ли стоить овчинка выделки?
пока я пребывал в царстве морфея без меня уже все рассказали :) но дело не только в скорости, а и в объеме. простой пример: одно поле int в таблице с 50000000. а плюс к этому по нему еще *обязательно* нужно будет создавать индекс. Диски и память хоть вещь и резиновая, но все нужно расходовать с умом :)
если у вас проблемы с тем, что индекс не помещается в память — то не нужно этим бравировать вынужденную денормализацию в схеме и предметной области. просто возьмите FEDERATED и пусть этой работой занимается mysql.
у меня вообще нет проблем :)
а если по тексту:
1) slovari.yandex.ru/dict/mikhelson/article/mi11/mi1-0617.htm — как следствие «вынужденной денормализацией»
2) ru.wikipedia.org/wiki/%D0%9D%D0%BE%D1%80%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F_%D1%84%D0%BE%D1%80%D0%BC%D0%B0. А теперь вопрос: какая именно форма нарушена разделением одной огромной таблицы на несколько помельче?
3) К вопросу об помериться скоростью выборки — предложение сделать кубического коня в вакууме, прошу прощения, и не имеет с реальной действительностью практически ничего общего. в реальности одновременно с выборкой конкретно в данную таблицу будет вестись вставка (хоть и с несколько меньшей интенсивностью), кешировать данные этой таблицы можно, но осторожно и не на долго. В контексте ORM к этой таблице будет десяток последовательных обращений. И при этом это будет делаться ну хотя бы раз 200-300 одновременно например в течении часа. наблюдения и опыт показывают, что разница может быть в пару порядков. да и почему сразу разговор про MySQL? Но если упираться в него, то есть еще как минимум шардинг.
4) ну и под конец читаем *внимательно* последнее предложение ;)
В нескольких проектах поступал так же, при записи\удалении в таблицу рейтинга, триггер обновлял поле рейтинг для конкретной сущности (что бы НЕ суммировать рейтинг при каждой выборке сущности). Пока полет нормальный!
Есть достаточно наивный вариант, чтобы не хранить информацию о типе записи в каждой таблице.

Создадим таблицу Entity (сущности), в которой будем хранить информацию о всех идентификаторах и типах. Дополнительно создадим таблицу EntityType для хранения данных о самом типе (например, имени типа 'name'). Структуру табиц буду записывать в формате: имя_таблицы: (список полей), PK(поле первичного ключа), FK (внешний ключ)

Entity: (id:autoincrement, type:int) , PK(id), FK(type -> EntityType.id)
EntityType: (id:int, name:string), PK(id), FK(id -> Entity.id)

Поле EntityType.id не генерирует значения идентификатора, а ссылается на Entity.id. Т.е. тип это тоже какая-то сущность.

Аналогично — пользователи это тоже сущности.
User: (id:int, firstname:string)
, PK(id), FK(id -> Entity.id)

Пользователем, который запостил статью, может быть не любая абстрактная сущность, а лишь некто из таблицы User:
Article: (id:int, user:int)
, PK(id), FK(id -> Entity.id), FK(user -> User.id)

Продолжим:
Comment: (id:int, user:int, item:int)
, PK(id), FK(id -> Entity.id), FK(user -> User.id), FK(item -> Entity.id)
Vote: (id:int, user:int, item:int)
, PK(id), FK(id -> Entity.id), FK(user -> User.id), FK(item -> Entity.id)

В таблице Comment поле item ссылается на абстрактный Entity.id. Поэтому комментировать можно любые сущности: статьи, пользователей, типы,…, какие-то другие сущности, которые появятся в будущем. Аналогично для Vote.

В приципе, на одну и ту же сущность могут ссылаться id из разных таблиц. Для хранения этих данных создадим таблицу Table, где в поле name будем хранить имя табицы. А для связи таблицы таблиц с таблицей сущностей используем отношение многие-к-многим через таблицу Entity2Table:
Entity2Table: (entity:int, class:int)
, PK(entity, class), FK(entity -> Entity.id, ), FK(class -> Table.id)
Table: (int:int, name:string)
, PK(id), FK(id -> Entity.id)

Таким образом для записи инфомации о новой сущности (допустим User) придется делать следующие вставки: в таблицу Entity, в таблицу User и в таблицу Entity2Table для связи вновь созданного юзера и таблицы User.

Такая структура отображается на соответствующие понятия ООП: Entity => Object, Table => Class, EntityType => DataType, Entity2Table => extends (classes).

зы: сори за сумбурность. это скорее мемори дамп.
ну вот и напутал. конечно же связывать нужно не сущности с таблицами, а их типы с таблицами. т.е. Entity2Table это бред. читать:
EntityType2Table: (entity_type:int, table:int)
, PK(entity_type, table), FK(entity_type -> EntityType.id, ), FK(table -> Table.id)
Table: (int:int, name:string)
, PK(id), FK(id -> Entity.id)

а extends из ООП extends это EntityType2Table.

В этом случае, для в ставки юзера потребуется лишь два запроса: в Entity и в User.
funca, есть реляционные модели, а то что вы предложили более походит на объектную модель
а, автору да, пять, несомненное открытие америки
И не лень же было рисовать схемки в графическом редакторе (явно не предназначенном для составления блок-схем судя по линиям и прочим деталям)
Sign up to leave a comment.

Articles