INSERS TRIGGERが処理されると、INNODBテーブルはどのようにロックされていますか?
-
12-10-2019 - |
質問
2つの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
テーブルには700kのレコードがあります)。
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.アプリケーションで2つのクエリを使用します
SELECT SUM(`vote`) FROM `votes` WHERE `article_id` = 1;
UPDATE `articles`
SET sum_votes = <1st_query_result>
WHERE `id` = 1;
1つ目の方法はきれいに見えますが、 選択クエリが実行される間ずっとテーブルはロックされますか?
解決
並行性の問題について、あなたは持っています '簡単' 2番目の方法で並行性の問題を防ぐ方法、トランザクション内で記事の選択を実行する( For update
今では暗黙的です)。同じ記事の同時挿入物は、この同じロックを取得できず、あなたを待ちます。
新しいデフォルトの分離レベルを使用すると、トランザクションでシリアル化レベルを使用することなく、トランザクションが終了するまで投票テーブルに同時挿入が表示されません。したがって、あなたの合計は一貫性のあるままでなければなりません またはコヒーレントのように見えます. 。しかし、同時トランザクションが同じ記事に投票を挿入し、あなたの前にコミットした場合(そして、この2番目の記事では挿入が表示されません)、コミットする最後のトランザクションはカウンターを上書きし、1票を失います。 したがって、前に選択を使用して記事で行ロックを実行します (そしてもちろん、トランザクションであなたの仕事をします)。テストし、MySQLで2つのインタラクティブセッションを開き、Beginでトランザクションを開始するのは簡単です。
トリガーを使用する場合、デフォルトでトランザクションにあります。ただし、記事テーブルの選択を実行して、実行中の同時トリガーの暗黙的な行ロックを作成する必要があると思います(テストが難しい)。
- 削除トリガーを忘れないでください。
- 更新トリガーを忘れないでください。
- トリガーを使用してコードを維持しない場合は、投票のすべての挿入/削除/更新クエリに注意してください。トランザクションの前に対応する記事で行ロックを実行する必要があります。忘れるのはそれほど難しくありません。
最後のポイント:トランザクションの使用を開始する前に、より難しいトランザクションを作成します。
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
この方法では、記事の行ロックは必要ありません。MySQLは、同じ行の潜在的な書き込みが発生し、終了するまで他のトランザクションをブロックすることを検出します。 ただし、以前のリクエストから計算したものを使用しないでください. 。更新クエリは、ロックが最初のトランザクションによってリリースされるときに、記事のロックリリースを待っています COMMIT
の計算 SUM
数えるためにもう一度行う必要があります。したがって、更新クエリにはを含める必要があります SUM
または追加します。
update articles set nb_votes=(SELECT count(*) from vote) where id=2;
ここでは、MySQLがスマートであることがわかります。挿入が同時に行われている間に2つのトランザクションがこれを実行しようとしている場合、デッドロックが検出されます。シリアル化レベルでは、次のような間違った値を取得する方法が見つかりませんでした。
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;
しかし、やり直す必要がある壊れた取引を処理する準備をしてください。
他のヒント
これを試して:
編集: スキーマを変更して、ユーザーが同じ画像に何度も投票できるようにしました。
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;