2016-06-09 10 views
2

Ich zog aus der global aussehenden Datenbank zur nächsten SQL-Abfrage. Ich hoffe, es funktioniert in nächsten Algorithmus: 1) Wählen Sie/nehmen Sie die Sperre von 1 Eintrag aus Tabelle Checkins. 2) Sperren und update usage_flag, so dass ich keine Sorgen über Race Condition.Wie nehmen Sie die Sperre für einen Eintrag?

  begin; 
      SELECT GetDistance('newhaven', area) as distance, id = (SELECT @proxy_id := id) 
       from checkins 
       WHERE last_checkin > DATE_SUB(NOW(), INTERVAL 5 MINUTE) 
        AND active = 1 
        AND offline = 0 
        AND usage_flag = 0 
      ORDER BY distance ASC, RAND() limit 1 FOR UPDATE; 
      UPDATE checkins set usage_flag=1 where [email protected]_id; 
      commit; 

und aber nach einem Tag in der Produktion, sehe ich eine Tonne Fehler:

'Lock wait timeout exceeded; try restarting transaction' 

Wie kann ich diesen Code verbessern, oder vielleicht völlig falsch ich in meinem Mutmaßungen, wie select for updatebegin-commit und arbeitet. Bitte korrigieren Sie mich oder Zeit mich eine Idee, wie es besser geht.

+0

Ihre Anfrage wird Ihre gesamte Tabelle sperren, weil Sie alle Zeilen müssen zum Beispiel berechnen die Bestellung. Ich gehe davon aus, dass Ihre Abfrage länger läuft, sodass Sie direkt ohne explizites Sperren aktualisieren können. Ist dieser Code in einer Prozedur? – Solarflare

+0

@Solarflare Vielen Dank für Ihre Antwort. '' 'Ist dieser Code in einer Prozedur?' '' Nein, es ist eine unabhängige Abfrage. '' 'so können Sie nur direkt ohne explizite Sperre aktualisieren' '' Ich habe eine 300+ Prozesse, die diese Abfrage verwenden, und nicht einen Eintrag auf einmal verwenden möchten, so dass ich definitiv sicher, ich brauche Sperre, aber nicht ganze Tabelle . Vielleicht haben Sie eine Idee, wie Sie es umsetzen können? – comalex3

Antwort

0

Ihre Abfrage wird grundsätzlich die gesamte Tabelle sperren, da sie alle Zeilen benötigt, um die Reihenfolge zu berechnen.

Ich nehme an, Ihre Abfrage läuft einige Zeit (sonst können Sie nur vor Ort aktualisieren), so dass Ihre Tabelle gesperrt ist, bis sie alle Entfernungen berechnet hat. Verbessere also entweder deine Distanzberechnung oder sperre sie während dieser Zeit nicht.

Um die Sperrzeit zu minimieren, können Sie die (zeitaufwendige) Auswahl und die Sperre für die Aktualisierung trennen. Wenn sich der Status während der Auswahl geändert hat, können Sie ihn außerhalb der Sperre wiederherstellen und es erneut versuchen.

Sie können z.B. Verwenden Sie die folgende Prozedur

delimiter $$ 

create procedure SetUsageflag() 
begin 
    set @LockCounter = 10; 

    repeat 
    set @LockCounter = @LockCounter - 1; 

    set @proxy_id = 
      (SELECT id from checkins 
      WHERE last_checkin > DATE_SUB(NOW(), INTERVAL 5 MINUTE) 
        AND active = 1 
        AND offline = 0 
        AND usage_flag = 0 
      ORDER BY GetDistance('newhaven', area) asc, RAND() 
      limit 1); 

    start transaction; 
    -- check if status is still 0, and lock that row 
    set @checkvalue = (select coalesce(usage_flag,0) 
         from checkins where id = @proxy_id for update); 
    if @checkvalue = 0 then 
     update checkins set usage_flag = 1 where id = @proxy_id; 
     set @LockCounter = 0; 
     -- return result to caller 
     SELECT GetDistance('newhaven', area) as distance, id 
     from checkins where id = @proxy_id; 
    end if ; 
    commit; 

    -- give up after some time 
    if @LockCounter = 1 then 
     SIGNAL SQLSTATE '45000' 
     SET MESSAGE_TEXT = 'Lock-Timeout trying to find an unused checkin!', MYSQL_ERRNO = 1205; 
    end if; 

    until (@LockCounter <= 1) end repeat; 
end $$ 

delimiter ; 

Wenn es oft passiert, dass Sie die select wiederholen haben, könnten Sie eine temporäre Tabelle verwenden möchten, und fügen Sie zum Beispiel die 10 besten Einträge und testen sie alle nacheinander, wenn die ersten fehlschlagen, so müssen Sie Ihre Berechnung nicht wiederholen (ich weiß nicht, wie lange es dauert).

0

Aber so, wie Sie diese strukturiert haben Sie die gesamte Tabelle sperren müssen
auswählen, um Grenze 1 müssen die Tabelle als eine Änderung sperren könnte die Ausgabe von der

Abfrage ändern

ich vermuten, dass dieses ist teuer

GetDistance('newhaven', area) 

können Sie diese Ausgabe in einer Spalte speichern

Was brauchen Sie wirklich? Sie müssen nur wissen, dass ein anderer Prozess usage_flag = 1 nicht gesetzt hat?

Ich weiß nicht, mysql, aber können Sie eine Zeile zählen auf diese?

UPDATE checkins set usage_flag=1 where [email protected]_id and usage_flag=0; 

Wenn ein anderer Prozess bereits auf 1 geändert, dann hätte dies rowcount von 0
Dies würde die (teure) Transaktion vermeiden

Verwandte Themen