Pregunta

Tengo dos tablas InnoDB:

artículos

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

califican

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

Cuando se introduce un nuevo registro en la tabla votes, quiero actualizar el campo sum_votes en la tabla articles mediante el cálculo de la suma de todos los votos.

La pregunta

¿Qué camino es más eficiente, si el () de cálculo SUMA en sí es muy pesada (tabla votes tiene 700K registros).

1. La creación de un disparador

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. El uso de dos consultas en mi aplicación

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

primera forma parece más limpio, pero será la tabla se bloquea todo el tiempo las carreras consulta de selección?

¿Fue útil?

Solución

Sobre los problemas de concurrencia, que tiene un manera 'fácil' para evitar cualquier problema de concurrencia en el segundo método, dentro de su transacción ejecutar una selección en la línea de los artículos (el For update ahora está implícita). Cualquier inserciones concurrentes en el mismo artículo no será capaz de obtener esta misma cerradura y le esperará.

Con los nuevos niveles de aislamiento por defecto, sin necesidad de utilizar nivel de serialización en la transacción que no vería ninguna inserciones concurrentes en la mesa de votación hasta el final de la transacción. Por lo que su SUMA debe permanecer coherente o se parece coherente . Pero si una transacción simultánea inserto un voto en el mismo artículo y se comprometen antes (y esto segundo no se ve su inserción), la última transacción para cometer sobrescribirá el contador y se le suelta a 1 voto. Así que realizan un bloqueo de registro sobre el artículo mediante el uso de una selección antes de (y hacer su trabajo en una transacción, por supuesto). Es fácil de prueba, abiertos 2 sesiones interactivas en MySQL y empezar a transacciones con BEGIN.

Si utiliza el gatillo se encuentra en una transacción por defecto. Pero creo que debería funcionar tan bien al seleccionar en la tabla artículo para hacer un bloqueo de registro implícito para desencadenantes concurrentes en funcionamiento (más difícil de la prueba).

  • No se olvide de los factores desencadenantes de borrado.
  • No se olvide de los factores desencadenantes de actualización.
  • Si usted no utiliza disparadores y estancia en el código, tenga cuidado de cada insertar / eliminar / Consulta de actualización en el voto debe realizar un bloqueo de registro en el correspondiente artículo antes en el transacción. No es muy difícil de olvidar uno.

El último punto: hacer transacciones más duros, antes de iniciar el uso de transacciones:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

De esta manera no es necesario la fila cerraduras de artículos, MySQL detectará que tiene lugar una potencial en la misma fila y bloqueará la transacción a otros hasta que termine. Pero no use algo que han calculado a partir de una solicitud anterior . La consulta de actualización estará esperando para una liberación del bloqueo sobre los artículos, cuando el bloqueo se libera por la primera transacción COMMIT el cálculo de SUM debe hacerse de nuevo para contar. Por lo que la consulta de actualización debe contener el SUM o hacer una adición.

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

Y aquí se verá que MySQL es inteligente, se detecta un punto muerto si 2 transacciones están tratando de hacer esto mientras inserción se ha realizado en un tiempo simultáneo. En los niveles de serialización no he encontrado una manera de obtener un valor de malo en:

   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;

Pero estar preparados para manejar transacciones de ruptura que se debe hacer de nuevo.

Otros consejos

intente lo siguiente:

PHP:? Estrella concepto de sistema de clasificación

EDIT: esquema cambiado para permitir a un usuario a voto para la misma imagen muchas veces:

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;
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top