Pregunta

¿Cómo se detienen las condiciones de carrera en MySQL? El problema en cuestión es causado por un algoritmo simple:

  1. seleccione una fila de la tabla
  2. si no existe, insértelo

y luego obtienes una fila duplicada, o si la evitas mediante claves únicas / primarias, un error.

Ahora normalmente creo que las transacciones ayudan aquí, pero debido a que la fila no existe, la transacción en realidad no ayuda (¿o me falta algo?).

LOCK TABLE suena como una exageración, especialmente si la tabla se actualiza varias veces por segundo.

La única otra solución que se me ocurre es GET_LOCK () para cada ID diferente, pero ¿no hay una mejor manera? ¿No hay problemas de escalabilidad aquí también? Y también, hacerlo para cada tabla suena un poco antinatural, ya que me parece un problema muy común en las bases de datos de alta concurrencia.

¿Fue útil?

Solución

lo que quieres es LOCK TABLES

o si eso parece excesivo, ¿qué tal INSERT IGNORE con una comprobación de que la fila se insertó realmente.

  

Si usa la palabra clave IGNORE, los errores   que ocurren mientras se ejecuta el INSERT   declaración se tratan como advertencias   en su lugar.

Otros consejos

Me parece que debería tener un índice único en su columna de identificación, por lo que una inserción repetida desencadenaría un error en lugar de ser aceptado ciegamente de nuevo.

Eso se puede hacer definiendo la identificación como clave principal o usando un índice único por sí mismo.

Creo que la primera pregunta que debes hacerte es ¿por qué tienes muchos hilos haciendo el mismo trabajo? ¿Por qué tendrían que insertar exactamente la misma fila?

Después de ser respondido, creo que ignorar los errores será la solución más eficaz, pero mida ambos enfoques (GET_LOCK v / s ignore los errores) y compruébelo usted mismo.

No hay otra manera que yo sepa. ¿Por qué quieres evitar errores? Aún debe codificar el caso cuando se produce otro tipo de error.

Como staticsan dice que las transacciones ayudan, pero, como generalmente están implícitas, si dos inserciones son ejecutadas por hilos diferentes, ambas estarán dentro de una transacción implícita y verán vistas consistentes de la base de datos.

Bloquear toda la mesa es realmente exagerado. Para obtener el efecto que desea, necesita algo que la literatura llama "bloqueos de predicados". Nadie los ha visto, excepto los impresos en el papel en el que se publican los estudios académicos. La siguiente mejor opción son las cerraduras en las "rutas de acceso". a los datos (en algunos DBMS: " bloqueos de página ").

Algunos sistemas que no son SQL le permiten hacer tanto (1) como (2) en una sola declaración, lo que significa más o menos las posibles condiciones de carrera derivadas de su sistema operativo que suspende su hilo de ejecución justo entre (1) y (2) , se eliminan por completo.

Sin embargo, en ausencia de bloqueos de predicados, tales sistemas aún necesitarán recurrir a algún tipo de esquema de bloqueo, y cuanto más fina sea la "granularidad". (/ " alcance ") de los bloqueos que requiere, mejor para la concurrencia.

(Y para concluir: algunos DBMS, especialmente aquellos por los que no tiene que pagar, de hecho no ofrecen una granularidad de bloqueo más fina que '' toda la tabla '').

A nivel técnico, una transacción ayudará aquí porque otros hilos no verán la nueva fila hasta que confirme la transacción.

Pero en la práctica eso no resuelve el problema, solo lo mueve . Su aplicación ahora necesita verificar si la confirmación falla y decidir qué hacer. Normalmente lo haría deshacer lo que hiciste y reiniciar la transacción porque ahora la fila será visible. Así es como se supone que funciona el programador basado en transacciones.

Me encontré con el mismo problema y busqué en la red por un momento :)

Finalmente se me ocurrió una solución similar al método para crear objetos del sistema de archivos en directorios compartidos (temporales) para abrir archivos temporales de forma segura:

$exists = $success = false;
do{
 $exists = check();// select a row in the table 
 if (!$exists)
  $success = create_record();
  if ($success){
   $exists = true;
  }else if ($success != ERROR_DUP_ROW){
    log_error("failed to create row not 'coz DUP_ROW!");
    break;
  }else{
    //probably other process has already created the record,
    //so try check again if exists
  }
}while(!$exists)

No tenga miedo de busy-loop , normalmente se ejecutará una o dos veces.

Evita filas duplicadas de manera muy simple al poner índices únicos en sus tablas. Eso no tiene nada que ver con CERRADURAS o TRANSACCIONES.

¿Le importa si un inserto falla porque es un duplicado? ¿Necesita ser notificado si falla? ¿O lo único que importa es que se insertó la fila, y no importa quién o cuántos insertos duplicados fallaron?

Si no le importa, todo lo que necesita es INSERT IGNORE . No hay necesidad de pensar en transacciones o bloqueos de tabla en absoluto.

InnoDB tiene bloqueo de nivel de fila automáticamente, pero eso solo se aplica a actualizaciones y eliminaciones. Tiene razón en que no se aplica a las inserciones. ¡No puedes bloquear lo que aún no existe!

Puede BLOQUEAR explícitamente toda la tabla. Pero si su propósito es evitar duplicados, entonces lo está haciendo mal. Nuevamente, use un índice único.

Si hay que hacer un conjunto de cambios y desea un resultado de todo o nada (o incluso un conjunto de resultados de todo o nada dentro de un resultado de todo o nada más grande), utilice las transacciones y Guardar puntos. Luego use ROLLBACK o ROLLBACK TO SAVEPOINT * savepoint_name * para deshacer los cambios, incluidas las eliminaciones, actualizaciones y insertos.

Las tablas

LOCK no reemplazan las transacciones, pero es su única opción con las tablas MyISAM, que no admiten transacciones. También puede usarlo con tablas InnoDB si el bloqueo de nivel de fila no es suficiente. Consulte esta página para obtener más información sobre utilizando transacciones con declaraciones de tabla de bloqueo.

Tengo un problema similar. Tengo una tabla que, en la mayoría de los casos, debería tener un valor único ticket_id, pero hay algunos casos en los que tendré duplicados; no es el mejor diseño, pero es lo que es.

  1. El usuario A verifica si el ticket está reservado, no lo está
  2. El usuario B verifica si el ticket está reservado, no lo está
  3. El usuario B inserta un registro 'reservado' en la tabla para ese ticket
  4. El usuario A inserta un registro 'reservado' en la tabla para ese ticket
  5. ¿El usuario B busca duplicados? Sí, ¿mi registro es más nuevo? Sí, déjalo
  6. Usuario ¿Un cheque por duplicado? Sí, ¿mi registro es más nuevo? No, bórralo

El usuario B ha reservado el ticket, el usuario A informa que el ticket ha sido tomado por otra persona.

La clave en mi caso es que necesita un desempate, en mi caso es la identificación de incremento automático en la fila.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top