consulta sobre a combinação de uma atualização e uma consulta de inserção em uma única consulta no mysql

9

Eu quero rastrear o histórico de alterações para um usuário, para que, sempre que ele altere seu perfil, eu precise pegar os dados antigos, armazenar no histórico e atualizar com novos dados.

Eu posso usar a selectpara obter os dados antigos, insertpara o histórico e, finalmente, updatepara alterar os dados.

posso ter tudo isso em uma única consulta no mysql sem usar procedimentos armazenados, gatilhos, etc. como usar bloqueios etc. se assim for, me dê uma pequena amostra.

Saravanan
fonte
11
@savaranan: Esta pergunta é digna de um +1, pois apresenta um forte lembrete aos DBAs e Desenvolvedores para usarem transações e aproveitarem ao máximo as propriedades ACID do banco de dados.
RolandoMySQLDBA
2
@savaranan: Para todos os efeitos, Jack forneceu a ÚNICA resposta plausível que existe. De fato, Jack Douglas deu um passo adicional e forçou um bloqueio intermitente em todas as linhas com id = 10 para proteção MVCC adicional ao fazer SELECT ... FOR UPDATE. Sua resposta acentua ainda mais o argumento que Jack e eu temos dito o tempo todo: um UPDATE e INSERT não podem ser, nem nunca são, uma única consulta, eles podem ser apenas uma transação única para o comportamento SQL que sua pergunta propõe.
RolandoMySQLDBA 31/07

Respostas:

13

Para fazer isso sem o risco de bloquear outro usuário tentando atualizar o mesmo perfil, ao mesmo tempo, você precisa bloquear a linha em t1primeiro lugar, em seguida, usar uma transação (como aponta Rolando nos comentários à sua pergunta):

start transaction;
select id from t1 where id=10 for update;
insert into t2 select * from t1 where id=10;
update t1 set id = 11 where id=10;
commit;
Jack diz que tenta topanswers.xyz
fonte
Isso é simplesmente brilhante para bloquear ainda mais todas as linhas com id = 10. Isso deve ser um +2. Tudo o que posso dar é um +1 !!!
RolandoMySQLDBA 31/07
1

Não acredito que haja uma maneira de combinar todas as três afirmações. A coisa mais próxima disso realmente não ajuda, e isso é um SET SELECT. Sua melhor aposta é um gatilho. Abaixo está um exemplo de um gatilho que eu costumo usar para manter exatamente essa trilha de auditoria (construída com PHP):

$trigger = "-- audit trigger --\nDELIMITER $ \n".
    "DROP TRIGGER IF EXISTS `{$prefix}_Audit_Trigger`$\n".
    "CREATE TRIGGER `{$prefix}_Audit_Trigger` AFTER UPDATE ON `$this->_table_name` FOR EACH ROW BEGIN\n";

foreach ($field_defs as $field_name => $field) {
    if ($field_name != $id_name) {
       $trigger .= "IF (NOT OLD.$field_name <=> NEW.$field_name) THEN \n".'INSERT INTO AUDIT_LOG ('.
                    'Table_Name, Row_ID, Field_Name, Old_Value, New_Value, modified_by, DB_User) VALUES'.
                    "\n ('$this->_table_name',OLD.$this->_id_name,'$field_name',OLD.$field_name,NEW.$field_name,".
                    "NEW.modified_by, USER()); END IF;\n";
    }
}
$trigger .= 'END$'."\n".'DELIMITER ;';
Bryan Agee
fonte
-3

Descobri que esta consulta funciona em servidores SQL e MySQL INSERT INTO t2 SELECT * FROM t1 WHERE id=10; UPDATE t1 SET id=11 WHERE id=10;

Espero que isso seja útil para outras pessoas também no futuro.

Saravanan
fonte
4
Esta não é realmente uma consulta. Na verdade, são duas consultas que devem ser tratadas como uma transação.
RolandoMySQLDBA
@rolandomysqldba: isso funciona bem como uma consulta única quando envio para um servidor db a partir do código do aplicativo, onde trato esse conjunto como uma consulta única. por que você diz isso?. você pode refutar esta com fortes razões ..
Saravanan
2
@saravanan: Aos olhos do InnoDB ou de qualquer RDBMS compatível com ACID (Oracle, SQLServer, PostreSQL, Sybase, etc.), é impossível chamar essas duas instruções SQL de uma consulta. Como um banco de dados compatível com ACID, eles seriam tratados como duas instruções. Por padrão, o InnoDB está com a confirmação automática ativada. A primeira instrução, INSERT, seria executada como uma única transação. Os dados do MVCC (Multiversioning Concurrency Control) seriam gerados para manter uma cópia dos dados originais na tabela t2, linha por linha. Se o MySQL travar durante a execução do INSERT, o InnoDB utilizará os dados do MVCC para reverter t2 para seu estado original.
RolandoMySQLDBA
11
@saravanan: Suponha que o INSERT funcionou com sucesso. Os dados resultantes do INSERT foram confirmados (com a confirmação automática ativada) e a tabela de proteção MVCC t2 é descartada. Quando você executa o UPDATE, o MVCC é gerado na tabela t1 e o UPDATE é executado. Se o MySQL travar durante a UPDATE, o InnoDB utilizará os dados do MVCC em T1 para reverter a UPDATE. Mesmo que o UPDATE altere apenas uma linha, existe a possibilidade de um em um milhão de mover registros de t1 para t2 com o id 10 e não alterar o id 10 para o id 11 em t1. Para evitar este cenário único, você precisa fazer o seguinte ...
RolandoMySQLDBA
@savaranan: Trate as duas instruções SQL como uma única transação. A maneira mais simples de fazer isso é: COMEÇAR; INSERIR EM t2 SELECT * FROM t1 ONDE id = 10; ATUALIZAÇÃO t1 SET id = 11 WHERE id = 10; COMMIT; O motivo mais forte para tratar as duas instruções SQL como uma única transação é o fato de que o MVCC criado para o INSERT permaneceria em vigor durante o UPDATE. Se ocorrer uma falha no MySQL durante o UPDATE dentro de uma transação (BEGIN; ... COMMIT; block), o MVCC reverterá todas as alterações para um estado consistente. Se ambos INSERT e UPDATE forem concluídos, o MVCC será descartado no último momento.
RolandoMySQLDBA