5

私は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_votesarticles

質問

votesSUM()計算自体が非常に重い場合(テーブルに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;

最初の方法はよりクリーンに見えますが、SELECTクエリが実行されている間はテーブルがロックされますか?

4

2 に答える 2

5

並行性の問題については、2番目の方法で並行性の問題を防ぐための「簡単な」方法があります。トランザクション内で、記事の行で選択を実行します(これFor updateは暗黙的です)。同じ記事への同時挿入は、この同じロックを取得できず、あなたを待ちます。

新しいデフォルトの分離レベルでは、トランザクションでシリアル化レベルを使用しなくても、トランザクションが終了するまで投票テーブルに同時挿入は表示されません。したがって、SUMはコヒーレントを維持するか、コヒーレントのように見える必要があります。ただし、同時トランザクションが同じ記事に投票を挿入し、あなたの前にコミットした場合(そして、この2番目のトランザクションには挿入が表示されない場合)、最後にコミットしたトランザクションによってカウンターが上書きされ、1票が失われます。したがって、前にselectを使用して記事の行ロックを実行します(もちろん、トランザクションで作業を行います)。テスト、MySQLで2つのインタラクティブセッションを開き、BEGINでトランザクションを開始するのは簡単です。

トリガーを使用する場合、デフォルトでトランザクションになります。ただし、同時トリガーを実行するための暗黙的な行ロックを作成するには、記事テーブルでselectも実行する必要があると思います(テストが困難です)。

  • 削除トリガーを忘れないでください。
  • 更新トリガーを忘れないでください。
  • トリガーを使用せず、コードにとどまる場合は、投票時のすべての挿入/削除/更新クエリが、トランザクションの前に対応する記事に対して行ロックを実行する必要があることに注意してください。1つを忘れるのはそれほど難しいことではありません。

最後のポイント:トランザクションの使用を開始する前に、より難しいトランザクションを作成します。

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;

ただし、やり直さなければならない中断トランザクションを処理する準備をしてください。

于 2011-01-19T12:56:52.097 に答える
1

これを試して:

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;
于 2011-01-19T09:51:58.927 に答える