Question

J'ai deux tables InnoDB:

articles

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

votes

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

Quand un nouvel enregistrement est inséré dans la table votes, je veux mettre à jour le champ sum_votes dans le tableau de articles en calculant la somme des voix.

La question

Quel chemin est plus efficace, si le calcul SOMME () lui-même est très lourd (table votes a 700K enregistrements).

1. Création d'un déclencheur

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. L'utilisation de deux requêtes dans mon application

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

1er chemin semble plus propre, mais la table est verrouillé tout le temps des pistes de requête SELECT?

Était-ce utile?

La solution

A propos des problèmes de concurrence, vous avez un « easy » façon d'éviter tout problème de concurrence dans la 2ème méthode, dans votre transaction effectuer une sélectionnez sur la ligne d'articles (le For update est maintenant implicite). Toute insertion simultanée sur le même article ne sera pas en mesure d'obtenir ce même verrou et vous attendra.

Avec les nouveaux niveaux d'isolement par défaut, sans même en utilisant le niveau de sérialisation dans la transaction que vous ne vois pas insérer en même temps sur la table de vote jusqu'à la fin de votre transaction. Donc, votre SUM doit rester cohérente ou ressemble cohérente . Mais si une transaction concurrente insertion d'un vote sur la même article et engagent avant (et ce 2ème ne voit pas votre insert), la dernière transaction à écrasera le compteur et vous perdrez 1 vote. Donc effectuer un verrou sur l'article en utilisant un select before (et faire votre travail dans une transaction, bien sûr). Il est facile de test, ouvert 2 sessions interactives sur MySQL et commencer les transactions avec BEGIN.

Si vous utilisez le déclencheur que vous êtes dans une transaction par défaut. Mais je pense que vous devriez faire aussi bien le sélectionner sur la table de l'article pour faire un verrou de ligne implicite pour les déclencheurs simultanés en cours d'exécution (plus difficile à tester).

  • Ne pas oublier les déclencheurs de suppression.
  • Ne pas oublier les déclencheurs de mise à jour.
  • Si vous ne l'utilisez pas et déclencheurs séjour dans le code, faites attention à chaque insertion / suppression / mise à jour requête vote doit effectuer un verrou sur la article avant dans le correspondant transaction. Ce n'est pas très difficile à oublier un.

Dernier point: effectuer des transactions plus difficiles, avant de commencer l'utilisation de la transaction:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

De cette façon, vous ne avez pas besoin ramez verrous sur les articles, MySQL détecte qu'une écriture potentiel sur la même ligne se produit et bloquer la transaction d'autres jusqu'à ce que vous avez terminé. Mais ne pas utiliser quelque chose que vous avez calculé à partir d'une demande antérieure . La requête de mise à jour sera en attente d'une libération de verrouillage sur les articles, lorsque le verrou est libéré par la 1ère transaction COMMIT le calcul des SUM doit être fait à nouveau compter. Ainsi, la requête de mise à jour doit contenir les SUM ou faire une addition.

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

Et là, vous verrez que MySQL est intelligent, un blocage est détecté si 2 transactions tentent de le faire en insertion a été fait en un temps simultané. Dans les niveaux de sérialisation, je ne l'ai pas trouvé un moyen d'obtenir une valeur erronée avec:

   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;

Mais être prêt à gérer la transaction de rupture que vous devez refaire.

Autres conseils

essayez ceci:

PHP: concept système de notation étoiles

EDIT: schéma modifié pour permettre à un utilisateur de voter pour la même image plusieurs fois:

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;
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top