2014-06-17 47 views
9

方案:我有一些觸發器跟蹤一個表的記錄數以及其他有用信息。這些觸發器會在此表上添加/刪除/更新時觸發,並負責將此信息寫入另一個補充表中。MySQL中的觸發器和表鎖定

現在,這些觸發器將運行在多線程環境中,我可能會同時訪問表。 我希望我能做出這樣的事情,但它是被禁止的(錯誤:錯誤代碼:1314 LOCK不允許在存儲過程中):

DELIMITER $$ 
DROP TRIGGER IF EXISTS del_alarmCount$$ 
CREATE TRIGGER del_alarmCount AFTER DELETE ON Alarm 
FOR EACH ROW 
BEGIN 
SET autocommit=0; 
LOCK TABLES AlarmCount WRITE, AlarmMembership READ; 
    UPDATE AlarmCount SET num = num - 1 
    WHERE RuleId = OLD.RuleId AND 
     MemberId = 0 AND 
     IsResolved = OLD.IsResolved; 

    UPDATE AlarmCount SET num = num - 1 
    WHERE RuleId = OLD.RuleId AND 
     IsResolved = OLD.IsResolved AND 
     MemberId IN (SELECT MemberId FROM AlarmMembership WHERE AlarmId=OLD.Id); 
COMMIT; 
UNLOCK TABLES; 
END $$ 
DELIMITER ; 

實現與這些鎖的目標(或替代結構)是:

  1. 避免兩個觸發器同時運行在AlarmCount表和更新相關的記錄寫(我想我可能有兩個觸發器運行警報表的不同記錄更新AlarmCount的同一記錄)
  2. 確保AlarmMembership表不會同時被修改(例如同時目標MemberId被刪除)。

任何建議非常歡迎!

回答

6

我認爲最好的方式來處理,這將是使用SELECT ... FOR這裏描述的更新模式:http://dev.mysql.com/doc/refman/5.0/en/innodb-locking-reads.html

參考:

Let us look at another example: We have an integer counter field in a table child_codes that we use to assign a unique identifier to each child added to table child. It is not a good idea to use either consistent read or a shared mode read to read the present value of the counter because two users of the database may then see the same value for the counter, and a duplicate-key error occurs if two users attempt to add children with the same identifier to the table.

Here, LOCK IN SHARE MODE is not a good solution because if two users read the counter at the same time, at least one of them ends up in deadlock when it attempts to update the counter.

To implement reading and incrementing the counter, first perform a locking read of the counter using FOR UPDATE, and then increment the counter. For example:

SELECT counter_field FROM child_codes FOR UPDATE; UPDATE child_codes 
SET counter_field = counter_field + 1; 

A SELECT ... FOR UPDATE reads the latest available data, setting exclusive locks on each row > it reads. Thus, it sets the same locks a searched SQL UPDATE would set on the rows.

。 。 。

Note Locking of rows for update using SELECT FOR UPDATE only applies when autocommit is disabled (either by beginning transaction with START TRANSACTION or by setting autocommit to 0. If autocommit is enabled, the rows matching the specification are not locked.

所以你的情況,你將取代

LOCK TABLES AlarmCount WRITE, AlarmMembership READ; 
    UPDATE AlarmCount SET num = num - 1 
    WHERE RuleId = OLD.RuleId AND 
     MemberId = 0 AND 
     IsResolved = OLD.IsResolved; 

的東西,如

SELECT num FROM AlarmCount WHERE RuleId = OLD.RuleId AND 
      MemberId = 0 AND 
      IsResolved = OLD.IsResolved FOR UPDATE; 
UPDATE AlarmCount SET num = num - 1; 

我說「像」,因爲它並不完全清楚,我什麼OLD.RuleId和OLD.IsResolved是引用。另外值得一從http://dev.mysql.com/doc/refman/5.0/en/innodb-locking-reads.html關注的是:

The preceding description is merely an example of how SELECT ... FOR UPDATE works. In MySQL, the specific task of generating a unique identifier actually can be accomplished using only a single access to the table:

UPDATE child_codes SET counter_field = LAST_INSERT_ID(counter_field + 
1); 
SELECT LAST_INSERT_ID(); 

The SELECT statement merely retrieves the identifier information (specific to the current connection). It does not access any table.

換句話說,你也許可以進一步訪問只是一次表優化這種模式......但同樣是關於您的架構的一些細節,我不太明白,我不確定我能否提供你需要的實際陳述。我認爲,如果你看一下SELECT ... FOR UPDATE,你會發現這個模式歸結爲什麼,以及你需要做什麼才能在你的環境中實現這個功能。

我應該提及的是,有一些存儲引擎環境和事務隔離級別您需要考慮。關於這個話題,SO在這裏有非常非常好的討論:When to use SELECT ... FOR UPDATE?

希望這有助於!

+1

我最終鎖定了調用者代碼,因此避免了潛在的衝突。但是,非常好的解決方案謝謝!我將在下次遇到同樣的問題時考慮這一點。 – DocDbg