Mysql
 sql >> Base de Dados >  >> RDS >> Mysql

Transação MySQL:SELECT + INSERT


O que você precisa é de bloqueio . As transações "não são estritamente necessárias" de fato.

Você pode escolher entre "bloqueio pessimista" e "bloqueio otimista". A decisão sobre qual dessas duas possibilidades cabe a você e deve ser avaliada basicamente considerando:
  • o nível de simultaneidade que você tem
  • a duração das operações obrigatórias no banco de dados
  • a complexidade de toda a operação

Vou recomendar a leitura destes dois para construir uma ideia das coisas envolvidas:

Um exemplo para explicar melhor


Isso talvez não seja tão elegante, mas é apenas um exemplo que mostra como é possível fazer tudo sem transação (e mesmo sem as restrições UNIQUE). O que é necessário fazer é usar o seguinte comando combinado INSERT + SELECT e após sua execução para verificar o número de linhas afetadas. Se o número de linhas afetadas for 1, então ocorreu de outra forma (se for 0), houve uma colisão e a outra parte venceu.
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 é um exemplo de Bloqueio Otimista obtido sem transações e com uma única operação SQL.

Como está escrito, tem o problema de que precisa haver pelo menos uma linha já no slot table para que funcione (caso contrário, a cláusula SELECT sempre retornará um conjunto de registros vazio e, nesse caso, nada é inserido mesmo se não houver colisões. Existem duas possibilidades para fazê-lo funcionar:
  • insira uma linha fictícia na tabela talvez com a data no passado

  • reescrever para que a cláusula FROM principal se refira a qualquer tabela que tenha pelo menos uma linha ou melhor crie uma pequena tabela (talvez chamada dummy ) com apenas uma coluna e apenas um registro nela e reescreva da seguinte forma (observe que não há mais necessidade da 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);
    

Aqui seguindo uma série de instruções que se você simplesmente copiar/colar mostra a ideia em ação. Eu assumi que você codifica data/hora em campos int como um número com os dígitos de data e 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 é claramente um exemplo extremo de Bloqueio Otimista, mas é muito eficiente no final porque tudo é feito com apenas uma instrução SQL e com baixa interação (troca de dados) entre o servidor de banco de dados e o código php. Além disso, praticamente não há travamento "real".

...ou com bloqueio pessimista


O mesmo código pode se tornar uma boa implementação de bloqueio pessimista apenas cercado por instruções explícitas de bloqueio/desbloqueio de tabela:
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;

É claro que neste caso (Bloqueio Pessimista) o SELECT e o INSERT podem ser separados e algum código php executado no meio. No entanto, esse código permanece muito rápido de executar (sem troca de dados com php, sem código php intermediário) e, portanto, a duração do bloqueio pessimista é a mais curta possível. Manter o Pessimistic Lock o mais curto possível é um ponto chave para evitar a lentidão do aplicativo.

De qualquer forma, você precisa verificar o valor de retorno do número de registros afetados para saber se foi bem-sucedido, pois o código é praticamente o mesmo e, portanto, você obtém as informações de sucesso/falha da mesma maneira.

Aqui http://dev.mysql.com/doc/ refman/5.0/en/insert-select.html eles dizem que "O MySQL não permite inserções simultâneas para instruções INSERT ... SELECT" então não deve ser necessário o Pessimistic Lock mas de qualquer forma esta pode ser uma boa opção se você achar que isso vai mudar em futuras versões do MySQL.

Estou "Otimista" que isso não vai mudar;-)