¿Cómo consultar y aumentar un valor (contador) de forma segura para subprocesos? (evitar condiciones de carrera)

10

En una tabla donde cada fila tiene un contador (solo un valor entero), necesito obtener el valor actual y aumentarlo al mismo tiempo .

Efectivamente, quiero hacer esto:

SELECT counter FROM table WHERE id=123
UPDATE table SET counter=counter+1 WHERE id=123

Pero hacer esto como dos consultas obviamente no es seguro para subprocesos: múltiples procesos que hacen lo mismo (en la misma fila) pueden obtener el mismo valor de contador. Necesito que todos sean únicos, por lo que cada proceso obtendría el valor actual real y lo aumentaría en uno.

Puedo pensar en una construcción donde implemente un bloqueo manual por fila, pero me pregunto si hay una manera más fácil de hacerlo.

Nueces De Cohete
fuente
utilizando transacciones tal vez?
ypercubeᵀᴹ

Respuestas:

15

¡Las declaraciones de actualización funcionan perfectamente bien sin la selección anterior! Dado que las declaraciones individuales son seguras por definición, incluso dos consultas de ACTUALIZACIÓN realizadas al mismo tiempo solo darán como resultado que la fila se incremente dos veces.

Si realmente desea seleccionar el valor para su script PHP, hacer algo con él y luego desea actualizar este valor de contador exacto, puede hacer lo siguiente:

BEGIN;
SELECT `counter` FROM `table` WHERE `id` = 123 FOR UPDATE;
UPDATE `table` SET `counter` = `counter`+1 WHERE `id` = 123;
COMMIT;

Esto inicia una nueva transacción, luego selecciona las filas que desea actualizar y las bloquea exclusivamente. Luego puede actualizarlos de forma segura sin preocuparse de que otros clientes cambien su contenido o incluso de acceder a las filas bloqueadas. Finalmente necesitas comprometer tus cambios.

También debe leer algo sobre los niveles de aislamiento . Es probable que no desee un valor como READ UNCOMMITTEDnivel de aislamiento. Todo lo demás debería estar bien para este caso de uso.

GhostGambler
fuente
¿Leí en otros lugares que UPDATE table SET counter = counter + 1es suficientemente atómico? ¿Todavía necesita las declaraciones de transacción que lo rodean?
CMCDragonkai
@CMCDragonkai Su consulta por sí sola es atómica, pero si seleccionó el valor antes y no utilizó FOR UPDATEy las transacciones, el valor que seleccionó podría ser diferente al que se utilizó en la consulta de actualización. Mi combinación de consultas bloquea la fila tan pronto como se selecciona el valor y, por lo tanto, garantiza que este valor de contador exacto se utilizará en la consulta de actualización.
GhostGambler
Ok, pero eso solo es necesario si hago otro trabajo que no sea incrementar, ¿verdad? Tal como está, una consulta de actualización atómica solitaria es lo suficientemente buena si eso es todo lo que quiero hacer.
CMCDragonkai
1
@CMCDragonkai Si no ejecuta otra consulta que toca la columna, está bien.
GhostGambler