sql >> Base de Datos >  >> RDS >> Mysql

Transacción MySQL:SELECCIONAR + INSERTAR

Lo que necesitas es bloqueo . De hecho, las transacciones "no son estrictamente necesarias".

Puede elegir entre "bloqueo pesimista" y "bloqueo optimista". La decisión sobre cuál de estas dos posibilidades depende de usted y debe evaluarse básicamente considerando:

  • el nivel de concurrencia que tiene
  • la duración de las operaciones que tienen que ser atómicas en la base de datos
  • la complejidad de toda la operación

Recomendaré leer estos dos para construir una idea de las cosas involucradas:

Un ejemplo para explicarlo mejor

Esto quizás no sea tan elegante, pero es solo un ejemplo que muestra cómo es posible hacer todo sin transacción (e incluso sin las restricciones ÚNICAS). Lo que se necesita hacer es usar el siguiente comando combinado INSERT + SELECT y después de su ejecución para verificar el número de filas afectadas. Si el número de filas afectadas es 1, entonces ha tenido éxito de lo contrario (si es 0) ha habido una colisión y la otra parte ha ganado.

INSERT INTO `slot` (`start`, `end`, `uid`, `group`, `message`, `devices_id`)
SELECT @startTime, @endTime, @uid, @group, @message, @deviceId
FROM `slot`
WHERE NOT EXISTS (
    SELECT `id` FROM `slot`
    WHERE `start` <= @endTime AND `end` >= @startTime
    AND `devices_id` = @deviceId)
GROUP BY (1);

Este es un ejemplo de Optimistic Locking obtenido sin transacciones y con una sola operación SQL.

Tal como está escrito, tiene el problema de que ya debe haber al menos una fila en el slot table para que funcione (de lo contrario, la cláusula SELECT siempre devolverá un conjunto de registros vacío y, en ese caso, no se inserta nada si no hay colisiones. Hay dos posibilidades para que funcione realmente:

  • inserte una fila ficticia en la tabla tal vez con la fecha en el pasado
  • reescriba para que la cláusula FROM principal se refiera a cualquier tabla que tenga al menos una fila o mejor cree una tabla pequeña (tal vez llamada dummy ) con solo una columna y solo un registro y reescribir de la siguiente manera (tenga en cuenta que ya no es necesaria la cláusula GROUP BY)

    INSERT INTO `slot` (`start`, `end`, `uid`, `group`, `message`, `devices_id`)
    SELECT @startTime, @endTime, @uid, @group, @message, @deviceId
    FROM `dummy`
    WHERE NOT EXISTS (
        SELECT `id` FROM `slot`
        WHERE `start` <= @endTime AND `end` >= @startTime
        AND `devices_id` = @deviceId);
    

Aquí siguiendo una serie de instrucciones que si simplemente copia/pega muestra la idea en acción. Supuse que codifica la fecha/hora en los campos int como un número con los dígitos de fecha y hora concatenados.

INSERT INTO `slot` (`start`, `end`, `uid`, `group`, `message`, `devices_id`)
VALUES (1008141200, 1008141210, 11, 2, 'Dummy Record', 14)

INSERT INTO `slot` (`start`, `end`, `uid`, `group`, `message`, `devices_id`)
SELECT 1408141206, 1408141210, 11, 2, 'Hello', 14
FROM `slot`
WHERE NOT EXISTS (
    SELECT `id` FROM `slot`
    WHERE `start` <= 1408141210 AND `end` >= 1408141206
    AND `devices_id` = 14)
GROUP BY (1);

INSERT INTO `slot` (`start`, `end`, `uid`, `group`, `message`, `devices_id`)
SELECT 1408141208, 1408141214, 11, 2, 'Hello', 14
FROM `slot`
WHERE NOT EXISTS (
    SELECT `id` FROM `slot`
    WHERE `start` <= 1408141214 AND `end` >= 1408141208
    AND `devices_id` = 14)
GROUP BY (1);

INSERT INTO `slot` (`start`, `end`, `uid`, `group`, `message`, `devices_id`)
SELECT 1408141216, 1408141220, 11, 2, 'Hello', 14
FROM `slot`
WHERE NOT EXISTS (
    SELECT `id` FROM `slot`
    WHERE `start` <= 1408141220 AND `end` >= 1408141216
    AND `devices_id` = 14)
GROUP BY (1);

SELECT * FROM `slot`;

Este es claramente un ejemplo extremo de bloqueo optimista, pero al final es muy eficiente porque todo se hace con una sola instrucción SQL y con poca interacción (intercambio de datos) entre el servidor de la base de datos y el código php. Además, prácticamente no hay bloqueo "real".

...o con bloqueo pesimista

El mismo código puede convertirse en una buena implementación de Pessimistc Locking simplemente rodeado de instrucciones explícitas de bloqueo/desbloqueo de tablas:

LOCK TABLE slot WRITE, dummy READ;

INSERT INTO `slot` (`start`, `end`, `uid`, `group`, `message`, `devices_id`)
SELECT @startTime, @endTime, @uid, @group, @message, @deviceId
FROM `dummy`
WHERE NOT EXISTS (
    SELECT `id` FROM `slot`
    WHERE `start` <= @endTime AND `end` >= @startTime
    AND `devices_id` = @deviceId);

UNLOCK TABLES;

Por supuesto, en este caso (Bloqueo pesimista), SELECCIONAR e INSERTAR podrían separarse y ejecutarse algún código php en el medio. Sin embargo, este código sigue siendo muy rápido de ejecutar (sin intercambio de datos con php, sin código php intermedio) y, por lo tanto, la duración del Pessimistic Lock es lo más breve posible. Mantener Pessimistic Lock lo más breve posible es un punto clave para evitar la ralentización de la aplicación.

De todos modos, debe verificar la cantidad de registros afectados que devuelven el valor para saber si tuvo éxito, ya que el código es prácticamente el mismo y, por lo tanto, obtiene la información de éxito/fracaso de la misma manera.

Aquí http://dev.mysql.com/doc/ refman/5.0/en/insertar-seleccionar.html dicen que "MySQL no permite inserciones simultáneas para declaraciones INSERT... SELECT" por lo que no debería ser necesario Pessimistic Lock, pero de todos modos, esta puede ser una buena opción si cree que esto cambiará en futuras versiones de MySQL.

Soy "Optimista" que esto no va a cambiar;-)