Как таблицы innodb блокируются при обработке триггера ON INSERT?

StackOverflow https://stackoverflow.com/questions/4733773

Вопрос

У меня есть две таблицы innodb:

статьи

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

голоса

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

При вставке новой записи в votes таблицу, я хочу обновить sum_votes поле в articles таблицу, подсчитав сумму всех голосов.

Вопрос

Какой способ более эффективен, если сам расчет SUM() очень тяжелый (votes таблица содержит 700 тыс. записей).

1.Создание триггера

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.Использование двух запросов в моем приложении

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

1-й способ кажется чище, но будет ли таблица заблокирована все время выполнения запроса SELECT?

Это было полезно?

Решение

Что касается проблем параллелизма, у вас есть 'легкий' Чтобы предотвратить любые проблемы параллелизма во втором методе, внутри вашей транзакции выполните выбор в строке статей ( For update теперь является неявным).Любая одновременная вставка в одну и ту же статью не сможет получить эту же блокировку и будет ждать вас.

С новыми уровнями изоляции по умолчанию, даже не используя уровень сериализации в транзакции, вы не увидите никаких одновременных вставок в таблицу голосования до конца вашей транзакции.Таким образом, ваша СУММА должна оставаться последовательной. или выглядит связно.Но если параллельная транзакция вставит голосование по той же статье и зафиксирует ее раньше вас (и эта вторая транзакция не увидит вашу вставку), последняя фиксируемая транзакция перезапишет счетчик, и вы потеряете 1 голос. Поэтому выполните блокировку строки в статье, используя выбор перед (и, конечно, выполняйте свою работу в транзакции).Это легко протестировать, открыть 2 интерактивных сеанса в MySQL и начать транзакции с помощью BEGIN.

Если вы используете триггер, вы по умолчанию участвуете в транзакции.Но я думаю, вам также следует выполнить выбор в таблице статей, чтобы сделать неявную блокировку строк для одновременно работающих триггеров (сложнее проверить).

  • Не забудьте удалить триггеры.
  • Не забывайте триггеры обновления.
  • Если вы не используете триггеры и остаетесь в коде, будьте осторожны с каждым запросом вставки/удаления/обновления в голосовании должны выполнять блокировку строки в соответствующей статье ранее в транзакции.Это не очень сложно забыть.

Последний пункт:делайте более сложные транзакции, перед началом транзакции используйте:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

Таким образом, вам не нужны блокировки строк в статьях, MySQL обнаружит, что происходит потенциальная запись в той же строке, и заблокирует остальные транзакции, пока вы не закончите. Но не используйте то, что вы вычислили по предыдущему запросу..Запрос на обновление будет ожидать снятия блокировки со статей, когда блокировка будет снята первой транзакцией. COMMIT вычисление SUM необходимо сделать еще раз для подсчета.Таким образом, запрос на обновление должен содержать SUM или сделайте дополнение.

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

И здесь вы увидите, что MySQL умен: взаимоблокировка обнаруживается, если две транзакции пытаются сделать это, в то время как вставка была выполнена одновременно.На уровнях сериализации я не нашел способа получить неправильное значение с помощью:

   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;

Но будьте готовы к нарушению транзакции, которую вам придется повторить.

Другие советы

попробуй это:

PHP:Концепция системы звездного рейтинга?

РЕДАКТИРОВАТЬ: изменена схема, чтобы пользователь мог голосовать за одно и то же изображение много раз:

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;
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top