Cómo evitar la condición de carrera en MySQL
transactions race-condition (1)
Tengo una condición de carrera potencial en una aplicación que estoy desarrollando, que me gustaría explicar y evitar en mi consulta.
Para resumir el flujo de la aplicación ...
Crea una nueva fila en la tabla de
entries
:INSERT INTO entries ( name, email ) VALUES ( ''Foo Bar'', ''[email protected]'' );
Averigüe si el Sr. Bar es un ganador al consultar una tabla de
prizes
:SELECT id FROM prizes WHERE various_time_conditions = ''met'' AND id NOT IN ( SELECT prize_id FROM entries );
Si él es un ganador, actualice su fila de entrada en consecuencia:
UPDATE entries SET prize_id = [prize id] WHERE id = [entry id];
Como cada premio solo puede entregarse una vez, necesito eliminar cualquier posibilidad de una condición de carrera donde otro proceso pueda consultar la tabla de premios y actualizar la tabla de entrada entre los pasos 2 y 3 anteriores.
He estado investigando un poco y he encontrado un montón de información sobre transacciones (todas mis tablas usan InnoDB) y el uso de la sintaxis SELECT ... FOR UPDATE
MySQL, pero no estoy seguro de cuál es la solución más adecuada para mí.
Vas a querer bloquear el registro del premio. Así que agrega un indicador de disponibilidad en la tabla de premios (quizás con un valor predeterminado) si no vas a usar algo como un winner_id. Algo como esto:
SELECT id FROM prizes WHERE ... AND available = 1 FOR UPDATE
Luego, configure la disponibilidad si asigna el premio:
UPDATE prizes SET available = 0 WHERE id = ...
Tendrá que envolver esto dentro de una transacción, por supuesto.
Asegúrese de que cada vez que compruebe si el premio está disponible, agregue AND available = 1 FOR UPDATE
a la consulta porque un SELECT
sin la FOR UPDATE
no va a esperar un bloqueo.