2010-03-21 3 views
6

Sono abbastanza nuovo a lavorare con database relazionali, ma ho letto alcuni libri e conosco le basi del buon design.Problema di efficienza mySQL - Come trovare il giusto equilibrio di normalizzazione ...?

Sono di fronte a una decisione di progettazione, e non sono sicuro di come continuare. Ecco una versione molto più semplificata di ciò che sto costruendo: le persone possono valutare le foto 1-5 e ho bisogno di visualizzare i voti medi sull'immagine tenendo traccia dei voti individuali. Ad esempio, 12 persone hanno votato 1, 7 persone hanno votato 2, etc. etc.

il freak normalizzazione di me inizialmente progettato la struttura della tabella come questa:

Table pictures 
id* | picture | userID | 

Table ratings 
id* | pictureID | userID | rating 

Con tutti i vincoli di chiave esterna e tutto il set come dovrebbero essere. Ogni volta che qualcuno valuta una foto, inserisco semplicemente un nuovo record in valutazioni e lo faccio.

Per trovare la valutazione media di una foto, che avevo appena eseguito qualcosa di simile:

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

Averlo configurazione in questo modo mi permette di scorrere le statistiche di fantasia per. Posso facilmente trovare chi ha valutato una certa immagine a 3, e cosa no.

Ora penso che se c'è un crapload di valutazioni (che è molto possibile in quello che sto realmente progettando), trovare la media diventerà molto costoso e doloroso.

L'utilizzo di una versione non normalizzata sembra più efficiente. ad esempio

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

Per calcolare la media, dovrei solo selezionare una singola riga. Sembra molto più efficiente, ma molto più brutto.

Qualcuno può indicarmi la direzione giusta di cosa fare? La mia ricerca iniziale mostra che devo "trovare il giusto equilibrio", ma come faccio a trovare questo equilibrio? Qualsiasi articolo o ulteriore informazione di lettura sarebbe apprezzato anche.

Grazie.

+0

Avete incorrere in problemi di prestazioni o semplicemente chiedere? – Pentium10

+0

Non ho ancora riscontrato problemi di prestazioni. Non voglio progettare qualcosa che si piegherà sotto carico potenzialmente elevato. – Foo

risposta

4

Il tuo approccio normalizzato ha molto senso, quello denormalizzato no.


Nella mia esperienza (Telco Performance Management, centinaia di migliaia di punti dati per 1/4 ora) si farebbe il seguente:

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

Table: ratings 
id* | pictureID | userID | rating 

Per telco il rating immagini sarebbe ricalcolato una volta al giorno, dovresti farlo periodicamente (esogni ora) o ogni volta che si inserisce (ricalcolo per l'immagine valutata, non l'intera tabella). Questo dipende dalla quantità di valutazioni che ottieni.


Nel telco anche a mantenere il rating aggiornato in quello che è il vostro tavolo 'immagini' e un timestamp 1/4h nella tabella voti, ma non credo che è necessario livello di dettaglio.


Il 'denormalizzazione' è quello di spostare un fatto calculateable (count (valutazione) e AVG (valutazione)) alla tabella immagini. Ciò risparmia i cicli della CPU, ma costa più spazio di archiviazione.

+0

+1, vorrei raccomandare lo stesso ... –

1

Che cosa contengono questi rating per classificare i campi? Il numero di voti ricevuti? Allora non saprai chi ha votato. Se hai davvero bisogno di denormalizzare, aggiungerei semplicemente un campo di "valutazione media" alla tabella delle immagini e aggiornalo ogni volta che viene votato un voto (e mantieni la tabella delle valutazioni così com'è).

Più in generale, non rimanere intrappolati nell'ottimizzazione prematura. Prova a scrivere uno script di prova che crei 100.000 immagini e 1 milione di voti (o qualsiasi altra cifra tu voglia sostenere), e vedere per quanto tempo richiede la tua query AVG. È probabile che sarà ancora molto veloce. Assicurati che la tabella "classificazioni" abbia un indice su pictureID, quindi il DB non deve attraversare il milione di righe.

+0

Grazie. Lo terro 'a mente. Mi concentrerò sulla scrittura di casi di test e vedremo come si esibirà la prossima volta. – Foo

1

In RDBMS mondo, denormalizzazione significa "Voglio aumentare l'efficienza delle query a costo di una maggiore manutenzione pur mantenendo modello correttezza"

Nel tuo caso, l'efficienza sarà leggermente aumentata infatti (in quanto tutti le valutazioni vengono sempre recuperate dalla stessa pagina di dati).

Ma per quanto riguarda la correttezza del modello?

Con questo modello, per prima cosa, non si sa chi ha fatto i voti (questa informazione non è più memorizzata) e, in secondo luogo, non è possibile valutare l'immagine più di cinque volte.

Dato che il modello iniziale non aveva nessuna di queste restrizioni, credo che questo tipo di denormalizzazione non sia ciò che si vuole veramente.

1

Un buon modo per godersi entrambi i mondi è utilizzare Mysql Trigger. http://dev.mysql.com/doc/refman/5.0/en/triggers.html

Ora aggiungere un trigger che, quando un utente valuta una foto, aggiornerà il parametro avg_rating nelle tabelle delle immagini. (usando la stessa selezione che hai indicato)

Ora, quando si seleziona, è possibile selezionare solo una tabella. Ed è sempre aggiornato. E se desideri ottenere le informazioni esatte su chi può valutare quale immagine puoi selezionare anche dalla tabella di valutazione.

2

questo è come vorrei affrontare il problema 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 ; 

Spero che questo aiuti :)