MySQL Efficiency Issue - Как найти правильный баланс нормализации ...? - PullRequest
6 голосов
/ 21 марта 2010

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

Я сталкиваюсь с дизайнерским решением и не знаю, какПродолжить.Вот очень упрощенная версия того, что я строю: люди могут оценивать фотографии 1-5, и мне нужно отображать среднее количество голосов на картинке, отслеживая отдельные голоса.Например, 12 человек проголосовали за 1, 7 человек проголосовали за 2 и т. Д. И т. Д.

Урод с моей нормализацией изначально разработал структуру таблицы следующим образом:

Table pictures
id* | picture | userID | 

Table ratings
id* | pictureID | userID | rating

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

Чтобы узнать среднюю оценку фотографии, я просто запускаю что-то вроде этого:

SELECT AVG(rating) FROM ratings WHERE pictureID = '5' GROUP by pictureID 

Если он настроен таким образом, я получаю статистику.Я легко могу найти, кто оценил определенную картинку на 3, а что нет.

Теперь я думаю, что если есть дерьмовый рейтинг (что очень возможно в том, что я на самом деле проектирую), то найти среднее значение будет очень дорого и мучительно.

Использование неНормализованная версия представляется более эффективной.Например:

Table picture
id | picture | userID | ratingOne | ratingTwo | ratingThree | ratingFour | ratingFive

Чтобы вычислить среднее значение, мне просто нужно выбрать одну строку.Это кажется намного более эффективным, но намного более уродливым.

Может ли кто-то указать мне правильное направление, что делать?Мои первые исследования показывают, что я должен «найти правильный баланс», но как мне найти этот баланс?Любые статьи или дополнительная информация для чтения также приветствуются.

Спасибо.

Ответы [ 5 ]

4 голосов
/ 21 марта 2010

Ваш нормализованный подход имеет большой смысл, денормализованный - нет.


По моему опыту (управление производительностью Telco, сотни тысяч точек данных за 1/4 часа) мы будем делать следующее:

Table: pictures
id* | picture | userID | avg_rating | rating_count

Table: ratings
id* | pictureID | userID | rating

Для телекоммуникационных компаний рейтинг картинок будет пересчитываться один раз в день, вы должны делать это периодически (например, ежечасно) или каждый раз, когда вставляете (пересчитывайте для оцененных картинок, а не всей таблицы). Это зависит от количества оценок, которые вы получаете.


В телефонной компании мы также храним дату рейтинга в таблице «ваших фотографий» и отметку времени 1/4 часа в таблице рейтингов, но я не думаю, что вам нужен такой уровень детализации.


«Денормализация» заключается в перемещении вычисляемого факта (count (rating) и avg (rating)) в таблицу изображений. Это экономит циклы процессора, но стоит больше памяти.

2 голосов
/ 21 марта 2010

так я бы подошел к проблеме http://pastie.org/879604

drop table if exists picture;
create table picture
( 
 picture_id int unsigned not null auto_increment primary key,
 user_id int unsigned not null, -- owner of the picture, the user who uploaded it
 tot_votes int unsigned not null default 0, -- total number of votes 
 tot_rating int unsigned not null default 0, -- accumulative ratings 
 avg_rating decimal(5,2) not null default 0, -- tot_rating / tot_votes
 key picture_user_idx(user_id)
)engine=innodb;

insert into picture (user_id) values 
 (1),(2),(3),(4),(5),(6),(7),(1),(1),(2),(3),(6),(7),(7),(5);


drop table if exists picture_vote;
create table picture_vote
( 
 picture_id int unsigned not null,
 user_id int unsigned not null,-- voter
 rating tinyint unsigned not null default 0, -- rating 0 to 5
 primary key (picture_id, user_id)
)engine=innodb;

delimiter #

create trigger picture_vote_before_ins_trig before insert on picture_vote
for each row
begin
 declare total_rating int unsigned default 0;
 declare total_votes int unsigned default 0;

 select tot_rating + new.rating, tot_votes + 1 into total_rating, total_votes 
   from picture where picture_id = new.picture_id;

 -- counts/stats
 update picture set
    tot_votes = total_votes, tot_rating = total_rating, 
    avg_rating = total_rating / total_votes
 where picture_id = new.picture_id;

 end#
 delimiter ;

надеюсь, это поможет:)

1 голос
/ 21 марта 2010

Хороший способ насладиться обоими мирами - использовать Mysql Trigger.http://dev.mysql.com/doc/refman/5.0/en/triggers.html

Теперь добавьте триггер, который, когда пользователь оценивает изображение, будет обновлять avg_rating в таблицах изображений.(используя тот же выбор, который вы указали)

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

1 голос
/ 21 марта 2010

В мире RDBMS денормализация означает «Я хочу повысить эффективность запросов за счет увеличения обслуживания , сохраняя при этом правильность модели »

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

Но как насчет корректности модели?

С этим дизайном вы, во-первых, не знаете, кто проголосовал (эта информация больше не сохраняется), и, во-вторых, не может оценить изображение более пяти раз.

Поскольку у вашей исходной модели не было ни одного из этих ограничений, я считаю, что этот вид денормализации не являетсячто вы действительно хотите.

1 голос
/ 21 марта 2010

Что бы эти рейтинги содержались в полях рейтинга пять?Количество полученных голосов?Тогда вы не будете знать, кто проголосовал.Если вам действительно нужно денормализовать, я просто добавлю поле «средний рейтинг» в таблицу изображений и обновлю его при каждом голосовании (и сохраню таблицу рейтингов как есть).

Подробнеекак правило, не попадайтесь на преждевременную оптимизацию.Попробуйте написать тестовый сценарий, который создает 100 000 изображений и 1 миллион оценок (или любой другой показатель, который вы хотите поддержать), и посмотрите, сколько времени занимает ваш запрос AVG.Скорее всего, все еще будет достаточно быстро.Убедитесь, что ваша таблица «оценок» имеет индекс для pictureID, поэтому БД не нужно пересекать миллион строк.

...