2014-06-17 14 views
9

Scenariusz: Mam kilka wyzwalaczy, które śledzą liczbę rekordów jednej tabeli, wraz z innymi użytecznymi informacjami. Te wyzwalacze są uruchamiane przy dodawaniu/usuwaniu/aktualizowaniu w tej tabeli i zajmują się pisaniem tych informacji w innej uzupełniającej tabeli.Wyzwalacze i blokada tabeli w MySQL

Teraz te wyzwalacze będą działać w środowisku wielowątkowym, w którym być może będę mieć równoczesny dostęp do tabel. Chciałbym zrobić coś takiego, ale to jest zabronione (błędu: error code: 1314. LOCK jest niedozwolone w procedurach przechowywanych):

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 ; 

cele do osiągnięcia tych Zamki (lub alternatywnie konstrukcje) są:

  1. Unikaj dwa wyzwalacze działa jednocześnie pisać na stole AlarmCount i aktualizacji związanych z ewidencji (chyba mogę mieć dwa wyzwalacze działa na różnych zapisów tabeli Alarm aktualizujących ten sam zapis AlarmCount)
  2. upewnić, Tabela AlarmMembership nie zmienia się w międzyczasie (np. docelowy element członkowski zostanie usunięty w międzyczasie).

Wszelkie porady są bardzo mile widziane!

Odpowiedz

6

Myślę, że najlepszym sposobem, aby obsłużyć to byłoby użyć SELECT ... FOR UPDATE wzorca opisanego tutaj: http://dev.mysql.com/doc/refman/5.0/en/innodb-locking-reads.html

Dla porównania:

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.

Tak więc w Twoim przypadku, należy zastąpić

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

z czymś

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

mówię "coś takiego", ponieważ to nie jest do końca jasne, co do mnie i OLD.RuleId OLD.IsResolved odwołuje się. Warto również zauważyć, ze http://dev.mysql.com/doc/refman/5.0/en/innodb-locking-reads.html jest:

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.

Innymi słowy, prawdopodobnie można zoptymalizować ten wzór dalej tylko dostępu tabelę raz ... ale znowu tam kilka szczegółów na temat swojego schematu, że nie podążają za mną i nie jestem pewien, czy mógłbym podać rzeczywiste oświadczenie, którego potrzebujesz. Myślę, że jeśli spojrzysz WYBIERZ ... NA AKTUALIZACJĘ, zobaczysz, do czego ten wzór się sprowadza i co musisz zrobić, aby działało to w twoim środowisku.

Muszę również wspomnieć, że istnieje pewne środowisko przechowywania i poziomy izolacji transakcji, które warto wziąć pod uwagę. Jest bardzo, bardzo dobra dyskusja na temat SO na ten temat tutaj: When to use SELECT ... FOR UPDATE?

Mam nadzieję, że to pomoże!

+1

Skończyło się na zablokowaniu kodu wywołującego, więc zapobieganie potencjalnym konfliktom. Ale, bardzo ładne rozwiązanie dzięki! Rozważę to na następny raz, mam ten sam problem. – DocDbg