2011-01-19 39 views
4

我有兩個InnoDB表:處理ON INSERT觸發器時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.在我的應用程序

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

1方式似乎更清潔,但將表被鎖定的全部時間用兩個查詢SELECT查詢運行?

回答

4

關於併發問題,你有一個'簡單'方法來防止第二種方法中的任何併發問題,在你的事務中執行一個選擇的ar斜線(For update現在是隱含的)。同一篇文章中的任何併發插入操作都無法獲得同樣的鎖定,並會等待您。

使用新的默認隔離級別,即使事務中沒有使用序列化級別,也不會在事務結束時在投票表上看到任何併發插入。所以你的SUM應該保持連貫或看起來像連貫。但是如果一個併發事務在同一篇文章上插入一個投票並且在你之前提交(並且這第二個沒有看到你的插入),那麼最後一個提交的事務將覆蓋該計數器,你將失去1票。 因此,通過使用之前的選擇來執行文章的行鎖定(當然,在事務中執行您的工作)。這很容易測試,在MySQL上打開2個交互式會話並使用BEGIN開始交易。

如果您使用觸發器,則默認處於事務中。但我認爲你應該同時執行文章表上的select來爲併發觸發器運行(難以測試)的隱式行鎖。

  • 不要忘記刪除觸發器。
  • 不要忘記更新觸發器。
  • 如果你不使用觸發器和代碼中留 ,要注意每 插入/刪除投票 /更新查詢應該執行在 交易之前對應的文章在 行鎖。 忘記了一個不是很難。

最後一點:讓困難的交易,從交易使用前:

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; 

但要準備好應對破壞交易,你必須重做。

1

試試這個:

PHP: Star rating system concept?

編輯:改變模式以允許用戶把票投給同一圖像多次:

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; 
+0

在給定的例子中,沒有必要每次都計算SUM(),因爲每個記錄只有一個投票。他們只在每個插頁上做了'+1'。我有不同的情況。 – 2011-01-19 09:54:22