Domanda

Ho due tabelle InnoDB:

Articoli

id     | title    | sum_votes
------------------------------
1      | art 1    | 5
2      | art 2    | 8
3      | art 3    | 35

voto

id     | article_id    | vote
------------------------------
1      | 1             | 1
2      | 1             | 2
3      | 1             | 2
4      | 2             | 10
5      | 2             | -2
6      | 3             | 10
7      | 3             | 15
8      | 3             | 12
9      | 3             | -2

Quando un nuovo record viene inserito nella tabella votes, voglio aggiornare il campo sum_votes nella tabella articles calcolando la somma di tutti i voti.

La domanda

Da che parte è più efficiente, se la somma () calcolo stesso è molto pesante (tabella votes ha 700K record).

1. Creazione di un trigger

CREATE TRIGGER `views_on_insert`
AFTER INSERT
ON `votes`
FOR EACH ROW
BEGIN
   UPDATE `articles` SET
       sum_votes = (
           SELECT SUM(`vote`)
           FROM `votes`
           WHERE `id` = NEW.article_id
       )
    WHERE `id` = NEW.article_id;
END;

2. Utilizzando due query nella mia applicazione

SELECT SUM(`vote`) FROM `votes` WHERE `article_id` = 1;
UPDATE `articles` 
   SET sum_votes = <1st_query_result> 
 WHERE `id` = 1;

1 ° strada sembra più pulito, ma sarà la tabella di essere bloccato per tutto il tempo le piste query di selezione?

È stato utile?

Soluzione

Circa i problemi di concorrenza, si dispone di un 'facile' modo per prevenire eventuali problemi di concorrenza nel 2 ° metodo, dentro la transazione eseguire un selezionare sulla linea di articoli (il For update ora è implicito). Ogni inserto simultanea sullo stesso articolo non sarà in grado di ottenere la stessa serratura e vi aspetterà.

Con i nuovi livelli di isolamento di default, senza nemmeno utilizzando il livello serializzazione nella transazione si sarebbe non vedere alcun inserto concorrente sul tavolo voto fino al termine della transazione. Così il vostro SUM deve rimanere coerente o assomiglia coerente . Ma se una transazione simultanea inserto un voto sul medesimo articolo e impegnano prima di (e questo secondo uno non vede il proprio inserto), l'ultima transazione a commettere sovrascrive il bancone e ti perdere 1 voto. Quindi eseguire un blocco di riga sull'articolo utilizzando un selezionare prima (e fare il vostro lavoro in una transazione, ovviamente). E 'facile da prova, aperte 2 sessioni interattive su MySQL e iniziare le transazioni con BEGIN.

Se si utilizza il grilletto ci si trova in una transazione per impostazione predefinita. Ma penso che si dovrebbe eseguire così l'selezionate sul tavolo articolo per fare un blocco di riga implicita per i trigger simultanei in esecuzione (più difficile da test).

  • Non dimenticare i trigger di eliminazione.
  • Non dimenticare trigger di aggiornamento.
  • Se non si utilizza trigger e soggiorno nel codice, fare attenzione a ogni inserimento / cancellazione / Query di aggiornamento sulla votazione deve eseguire un blocco di riga sulla corrispondente articolo prima della transazione. Non è molto difficile dimenticare uno.

Ultimo punto: rendere le operazioni più difficili, prima di iniziare l'uso delle transazioni:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

In questo modo non è necessario remate serrature su articoli, MySQL rileverà che un potenziale di scrittura sulla stessa riga si verifica e bloccherà l'operazione di altri fino al completamento. Ma non usare qualcosa che avete calcolato da una precedente richiesta . La query di aggiornamento sarà in attesa di un rilascio sulla articoli, quando il blocco viene rilasciato dal 1 ° COMMIT transazione di calcolo di SUM dovrebbe essere fatto di nuovo a contare. Quindi la query di aggiornamento dovrebbe contenere la SUM o fare un'aggiunta.

update articles set nb_votes=(SELECT count(*) from vote) where id=2; 

E qui vedrete che MySQL è intelligente, viene rilevata una situazione di stallo se 2 transazioni stanno cercando di fare questo mentre inserto è stato fatto in un tempo concorrente. Nei livelli di serializzazione non ho trovato un modo per ottenere un valore di sbagliato in:

   SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
   BEGIN;
       insert into vote (...
       update articles set nb_votes=(
         SELECT count(*) from vote where article_id=xx
       ) where id=XX;
    COMMIT;

Ma essere pronti a gestire un'operazione di rottura che si deve rifare.

Altri suggerimenti

provare questo:

PHP:? Stella concetto di sistema di rating

Modifica dello schema modificato per consentire a un utente di votare per la stessa immagine più volte:

drop table if exists image;
create table image
(
image_id int unsigned not null auto_increment primary key,
caption varchar(255) not null,
num_votes int unsigned not null default 0,
total_score int unsigned not null default 0,
rating decimal(8,2) not null default 0
)
engine = innodb;

drop table if exists image_vote;
create table image_vote
(
vote_id int unsigned not null auto_increment primary key,
image_id int unsigned not null,
user_id int unsigned not null,
score tinyint unsigned not null default 0,
key (image_id, user_id)
)
engine=innodb;

delimiter #

create trigger image_vote_after_ins_trig after insert on image_vote
for each row
begin
 update image set 
    num_votes = num_votes + 1,
    total_score = total_score + new.score,
    rating = total_score / num_votes  
 where 
    image_id = new.image_id;
end#

delimiter ;

insert into image (caption) values ('image 1'),('image 2'), ('image 3');

insert into image_vote (image_id, user_id, score) values
(1,1,5),(1,2,4),(1,3,3),(1,4,2),(1,5,1),(1,5,2),(1,5,3),
(2,1,2),(2,2,1),(2,3,4),(2,3,2),
(3,1,4),(3,5,2);

select * from image;
select * from image_vote;
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top