Como testar uma instrução SQL Update antes de executá-la?

94

Em alguns casos, a execução de uma instrução UPDATE na produção pode salvar o dia. No entanto, uma atualização do Borked pode ser pior do que o problema inicial.

Além de usar um banco de dados de teste, quais são as opções para dizer o que uma instrução de atualização fará antes de executá-la?

static_rtti
fonte

Respostas:

49

Além de usar uma transação, como Imad disse (que deve ser obrigatória de qualquer maneira), você também pode fazer uma verificação de sanidade de quais linhas são afetadas executando um select usando a mesma cláusula WHERE de UPDATE.

Então, se você UPDATE é

UPDATE foo
  SET bar = 42
WHERE col1 = 1
  AND col2 = 'foobar';

O seguinte mostrará quais linhas serão atualizadas:

SELECT *
FROM foo
WHERE col1 = 1
  AND col2 = 'foobar';
um cavalo sem nome
fonte
1
Usar transações é melhor para verificar os dados então. Supondo que ele queira verificar o resultado, concluo que sua declaração é mais complexa do que 'SET bar = 42', portanto, em sua sessão, ele poderá fazer várias consultas para testar o conjunto de dados resultante ...
Imad Moqaddem
3
@ImadMoqaddem: Eu concordo e é por isso que escrevi " Além de usar uma transação como Imad disse "
a_horse_with_no_name
E se FOREIGN KEY UPDATE CASCADEseu sql falhar
Verde,
@Green: o que você quer dizer com "falhar"?
a_horse_with_no_name
73

E sobre as transações? Eles têm o recurso ROLLBACK.

@see https://dev.mysql.com/doc/refman/5.0/en/commit.html

Por exemplo:

START TRANSACTION;
SELECT * FROM nicetable WHERE somthing=1;
UPDATE nicetable SET nicefield='VALUE' WHERE somthing=1;
SELECT * FROM nicetable WHERE somthing=1; #check

COMMIT;
# or if you want to reset changes 
ROLLBACK;

SELECT * FROM nicetable WHERE somthing=1; #should be the old value

Resposta à pergunta de @rickozoe abaixo:

Em geral, essas linhas não serão executadas uma vez. Em PHP, você escreveria algo assim (talvez um pouco mais limpo, mas queria uma resposta rápida ;-)):

$MysqlConnection->query('START TRANSACTION;');
$erg = $MysqlConnection->query('UPDATE MyGuests SET lastname='Doe' WHERE id=2;');
if($erg)
    $MysqlConnection->query('COMMIT;');
else
    $MysqlConnection->query('ROLLBACK;');

Outra maneira seria usar variáveis ​​MySQL (consulte https://dev.mysql.com/doc/refman/5.7/en/user-variables.htm l e https://stackoverflow.com/a/18499823/1416909 ):

# do some stuff that should be conditionally rollbacked later on

SET @v1 := UPDATE MyGuests SET lastname='Doe' WHERE id=2;
IF(v1 < 1) THEN
    ROLLBACK;
ELSE
    COMMIT;
END IF;

Mas eu sugiro usar os wrappers de linguagem disponíveis em sua linguagem de programação favorita.

Marcel Lange
fonte
1
Isso terá resultados inesperados com transações aninhadas.
scones
Você pode dar um exemplo?
Marcel Lange
@JCM e outros, como você pode saber se a instrução de atualização foi bem-sucedida na linha 3 para que você possa confirmar e reverter?
ricko zoe
56

Autocommit OFF ...

MySQL

set autocommit=0;

Ele ativa o autommit off para a sessão atual.

Você executa sua instrução, vê o que mudou e então faz o rollback se estiver errado ou efetua o commit se for o que você esperava!

EDITAR: A vantagem de usar transações em vez de executar a consulta de seleção é que você pode verificar o conjunto resultante mais facilmente.

Imad Moqaddem
fonte
4
@dystroy: todo SGBD sensato suporta transações.
a_horse_with_no_name
7
Apenas lembre-se de confirmar ou reverter a transação rapidamente, ou você corre o risco de bloquear outras transações - e, no pior dos casos, parar o seu aplicativo. Não é uma boa ideia executar a consulta, depois almoçar e depois voltar para ver os resultados! :-)
Gary McGill
@GaryMcGill: a transação pendente iria (pelo menos no DBMS moderno) apenas bloquear outras transações de gravação .
a_horse_with_no_name
5
@dystroy: Infelizmente, MyISAM é usado em todos os lugares, e eu não sou o DBA.
static_rtti
1
Instrução
11

Eu sei que isso é uma repetição de outras respostas, mas tem algum suporte emocional para dar um passo extra para atualização de teste: D

Para atualização de teste, hash # é seu amigo.

Se você tiver uma declaração de atualização como:

UPDATE 
wp_history
SET history_by="admin"
WHERE
history_ip LIKE '123%'

Você hash UPDATE e SET para teste e, em seguida, hash de volta:

SELECT * FROM
#UPDATE
wp_history
#SET history_by="admin"
WHERE
history_ip LIKE '123%'

Funciona para declarações simples.

Uma solução adicional praticamente obrigatória é obter uma cópia (duplicado de backup), sempre que utilizar atualização em uma tabela de produção. Phpmyadmin> operações> cópia: table_yearmonthday. Leva apenas alguns segundos para tabelas <= 100M.

João
fonte
5

Não é uma resposta direta, mas já vi muitas situações de dados prod borked que poderiam ter sido evitadas digitando a WHEREcláusula primeiro ! Às vezes, um WHERE 1 = 0pode ajudar a montar uma declaração de trabalho com segurança também. E observar um plano de execução estimado, que estimará as linhas afetadas, pode ser útil. Além disso, em uma transação que você reverte como outros já disseram.

David M
fonte
2
@SystemParadox - nada, embora WHERE 1 = 0seja mais portátil se alguém encontrar isso trabalhando com um DBMS diferente. Por exemplo, o SQL Server não aceita WHERE FALSE.
David M
2

Nestes casos que você deseja testar, é uma boa ideia se concentrar apenas nos valores da coluna atual e que serão atualizados em breve .

Por favor, dê uma olhada no seguinte código que escrevi para atualizar os preços WHMCS:

# UPDATE tblinvoiceitems AS ii

SELECT                        ###  JUST
    ii.amount AS old_value,   ###  FOR
    h.amount AS new_value     ###  TESTING
FROM tblinvoiceitems AS ii    ###  PURPOSES.

JOIN tblhosting AS h ON ii.relid = h.id
JOIN tblinvoices AS i ON ii.invoiceid = i.id

WHERE ii.amount <> h.amount   ### Show only updatable rows

# SET ii.amount = h.amount

Desta forma, comparamos claramente os valores já existentes com os novos valores.

Mohammad Naji
fonte
1

Execute a consulta de seleção na mesma tabela com todas as wherecondições que você está aplicando na consulta de atualização.

manurajhada
fonte
0

faça um SELECTdisso,

como se você tivesse

UPDATE users SET id=0 WHERE name='jan'

convertê-lo para

SELECT * FROM users WHERE name='jan'

EaterOfCode
fonte