Come tabelle InnoDB sono bloccate quando sull'inserto grilletto viene elaborato?
-
12-10-2019 - |
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?
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 ??em>. 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;