Várias atualizações no MySQL


388

Eu sei que você pode inserir várias linhas ao mesmo tempo, existe uma maneira de atualizar várias linhas ao mesmo tempo (como em uma consulta) no MySQL?

Editar: Por exemplo, tenho o seguinte

Name   id  Col1  Col2
Row1   1    6     1
Row2   2    2     3
Row3   3    9     5
Row4   4    16    8

Quero combinar todas as atualizações a seguir em uma consulta

UPDATE table SET Col1 = 1 WHERE id = 1;
UPDATE table SET Col1 = 2 WHERE id = 2;
UPDATE table SET Col2 = 3 WHERE id = 3;
UPDATE table SET Col1 = 10 WHERE id = 4;
UPDATE table SET Col2 = 12 WHERE id = 4;

Respostas:


651

Sim, isso é possível - você pode usar INSERIR ... NA DUPLICATE KEY UPDATE.

Usando seu exemplo:

INSERT INTO table (id,Col1,Col2) VALUES (1,1,1),(2,2,3),(3,9,3),(4,10,12)
ON DUPLICATE KEY UPDATE Col1=VALUES(Col1),Col2=VALUES(Col2);

22
Se não houver duplicatas, não quero que essa linha seja inserida. o que eu deveria fazer? porque estou buscando informações de outro site que mantém tabelas com IDs. Estou inserindo valores com relação a esse id. se o site tiver novos registros, acabarei inserindo apenas os IDs e a contagem, exceto todas as outras informações. se e somente se houver uma entrada para o ID, ele deve ser atualizado; caso contrário, deve pular. o que devo fazer?
Jayapal Chandran

33
Nota: esta resposta também assume que o ID é a chave primária
JM4 12/12/12

11
@JayapalChandran, você deve usar INSERIR IGNORE junto com ON DUPLICATE KEY UPDATE. dev.mysql.com/doc/refman/5.5/en/insert.html
Haralan Dobrev

16
@HaralanDobrev O uso de INSERT IGNORE ainda insere os registros não duplicados. que Jayapal queria evitar. INSERT IGNORE apenas transforma qualquer erro em aviso :( stackoverflow.com/questions/548541/…
Takehiro Adachi 24/13

2
Esta resposta assume que o ID é uma chave exclusiva (pode ser primária, como já foi dito), mas o mais importante é que assume que não há outras chaves exclusivas. Se houver, ele pode jogar uma chave inglesa nos trabalhos.
21413 Steve Horvath

129

Como você possui valores dinâmicos, é necessário usar um IF ou CASE para que as colunas sejam atualizadas. Fica meio feio, mas deve funcionar.

Usando seu exemplo, você pode fazê-lo como:

Tabela UPDATE SET Col1 = ID do CASO 
                          QUANDO 1 ENTÃO 1 
                          QUANDO 2 ENTÃO 2 
                          QUANDO 4 ENTÃO 10 
                          ELSE Col1 
                        FIM, 
                 Col2 = ID do CASO 
                          QUANDO 3 ENTÃO 3 
                          QUANDO 4 ENTÃO 12 
                          ELSE Col2 
                        FIM
             ONDE id IN (1, 2, 3, 4);

talvez não tão bem para escrever para atualização dinâmica, mas interessante olhar para a funcionalidade da caixa ...
me_

11
@ user2536953, também pode ser bom para atualizações dinâmicas. Por exemplo, eu usei essa solução em loop no php:$commandTxt = 'UPDATE operations SET chunk_finished = CASE id '; foreach ($blockOperationChecked as $operationID => $operationChecked) $commandTxt .= " WHEN $operationID THEN $operationChecked "; $commandTxt .= 'ELSE id END WHERE id IN ('.implode(', ', array_keys(blockOperationChecked )).');';
Boolean_Type

86

A pergunta é antiga, mas gostaria de estender o tópico com outra resposta.

O que quero dizer é que a maneira mais fácil de conseguir isso é apenas agrupar várias consultas com uma transação. A resposta aceita INSERT ... ON DUPLICATE KEY UPDATEé um bom truque, mas é preciso estar ciente de suas desvantagens e limitações:

  • Como foi dito, se você iniciar a consulta com linhas cujas chaves primárias não existem na tabela, a consulta inserirá novos registros "semi-cozidos". Provavelmente não é o que você quer
  • Se você possui uma tabela com um campo não nulo sem valor padrão e não deseja tocar nesse campo na consulta, receberá um "Field 'fieldname' doesn't have a default value"aviso do MySQL, mesmo que não insira uma única linha. Isso causará problemas se você decidir ser rigoroso e transformar os avisos do mysql em exceções de tempo de execução no seu aplicativo.

Fiz alguns testes de desempenho para três das variantes sugeridas, incluindo a INSERT ... ON DUPLICATE KEY UPDATEvariante, uma variante com a cláusula "case / when / then" e uma abordagem ingênua com a transação. Você pode obter o código python e os resultados aqui . A conclusão geral é que a variante com declaração de caso é duas vezes mais rápida que duas outras variantes, mas é muito difícil escrever um código correto e seguro para injeção, portanto, eu pessoalmente adiro à abordagem mais simples: usar transações.

Edit: As descobertas de Dakusan provam que minhas estimativas de desempenho não são muito válidas. Por favor, veja esta resposta para outra pesquisa mais elaborada.


Usando transações, dica muito agradável (e simples)!
MTorres

E se minhas tabelas não forem do tipo InnoDB?
TomeeNS

11
Alguém poderia fornecer um link para como essas transações se parecem? E / ou código para um código seguro para injeção para a variante com declaração de caso?
François M.

11
Acho as informações fornecidas sobre velocidade neste post falsas. Eu escrevi sobre isso em um post abaixo. stackoverflow.com/questions/3432/multiple-updates-in-mysql/…
Dakusan

11
@ Dakusan, ótima resposta. Muito obrigado por estender, comentar e corrigir meus resultados.
Roman Imankulov

72

Não sei por que outra opção útil ainda não foi mencionada:

UPDATE my_table m
JOIN (
    SELECT 1 as id, 10 as _col1, 20 as _col2
    UNION ALL
    SELECT 2, 5, 10
    UNION ALL
    SELECT 3, 15, 30
) vals ON m.id = vals.id
SET col1 = _col1, col2 = _col2;

4
Isso é o melhor. Especialmente se você estiver obtendo os valores para atualização de outra consulta SQL como eu estava fazendo.
v010dya

11
Isso foi ótimo para uma atualização em uma tabela com uma quantidade enorme de colunas. Provavelmente usarei muito essa consulta no futuro. Obrigado!
Casper Wilkes

Eu tentei esse tipo de consulta. Mas quando os registros atingem o limite de 30k, o servidor parou. existe alguma outra solução?
Bhavin Chauhan

Isso parece ótimo. Vou tentar combinar isso com uma cláusula WHERE em que as chaves primárias não são atualizadas, mas usadas para identificar as colunas a serem alteradas.
nl-x

@BhavinChauhan Você tentou usar uma tabela temporária em vez do join-select para contornar o problema?
nl-x

41

Todos os itens a seguir se aplicam ao InnoDB.

Sinto que é importante conhecer a velocidade dos três métodos diferentes.

Existem 3 métodos:

  1. INSERT: INSERT com ON DUPLICATE KEY UPDATE
  2. TRANSACTION: onde você faz uma atualização para cada registro dentro de uma transação
  3. CASO: No qual você solicita / quando, para cada registro diferente dentro de um UPDATE

Acabei de testar isso, e o método INSERT era 6,7x mais rápido do que o método TRANSACTION. Eu tentei em um conjunto de 3.000 e 30.000 linhas.

O método TRANSACTION ainda precisa executar cada consulta individualmente, o que leva tempo, apesar de agrupar os resultados na memória, ou algo assim, durante a execução. O método TRANSACTION também é bastante caro nos logs de replicação e consulta.

Pior ainda, o método CASE foi 41,1x mais lento que o INSERT com 30.000 registros (6,1x mais lento que TRANSACTION). E 75x mais lento no MyISAM. Os métodos INSERT e CASE chegaram a ~ 1.000 registros. Mesmo com 100 registros, o método CASE é MUITO MAIS rápido.

Portanto, em geral, acho que o método INSERT é melhor e mais fácil de usar. As consultas são menores e mais fáceis de ler e ocupam apenas 1 consulta de ação. Isso se aplica ao InnoDB e ao MyISAM.

Material bônus:

A solução para o problema da não-default-campo INSERT está para desligar temporariamente os modos SQL relevantes: SET SESSION sql_mode=REPLACE(REPLACE(@@SESSION.sql_mode,"STRICT_TRANS_TABLES",""),"STRICT_ALL_TABLES",""). Salve o sql_modeprimeiro se planeja revertê-lo.

Como em outros comentários que eu vi, dizem que o auto_increment sobe usando o método INSERT, esse parece ser o caso no InnoDB, mas não no MyISAM.

O código para executar os testes é o seguinte. Também gera arquivos .SQL para remover a sobrecarga do interpretador php

<?
//Variables
$NumRows=30000;

//These 2 functions need to be filled in
function InitSQL()
{

}
function RunSQLQuery($Q)
{

}

//Run the 3 tests
InitSQL();
for($i=0;$i<3;$i++)
    RunTest($i, $NumRows);

function RunTest($TestNum, $NumRows)
{
    $TheQueries=Array();
    $DoQuery=function($Query) use (&$TheQueries)
    {
        RunSQLQuery($Query);
        $TheQueries[]=$Query;
    };

    $TableName='Test';
    $DoQuery('DROP TABLE IF EXISTS '.$TableName);
    $DoQuery('CREATE TABLE '.$TableName.' (i1 int NOT NULL AUTO_INCREMENT, i2 int NOT NULL, primary key (i1)) ENGINE=InnoDB');
    $DoQuery('INSERT INTO '.$TableName.' (i2) VALUES ('.implode('), (', range(2, $NumRows+1)).')');

    if($TestNum==0)
    {
        $TestName='Transaction';
        $Start=microtime(true);
        $DoQuery('START TRANSACTION');
        for($i=1;$i<=$NumRows;$i++)
            $DoQuery('UPDATE '.$TableName.' SET i2='.(($i+5)*1000).' WHERE i1='.$i);
        $DoQuery('COMMIT');
    }

    if($TestNum==1)
    {
        $TestName='Insert';
        $Query=Array();
        for($i=1;$i<=$NumRows;$i++)
            $Query[]=sprintf("(%d,%d)", $i, (($i+5)*1000));
        $Start=microtime(true);
        $DoQuery('INSERT INTO '.$TableName.' VALUES '.implode(', ', $Query).' ON DUPLICATE KEY UPDATE i2=VALUES(i2)');
    }

    if($TestNum==2)
    {
        $TestName='Case';
        $Query=Array();
        for($i=1;$i<=$NumRows;$i++)
            $Query[]=sprintf('WHEN %d THEN %d', $i, (($i+5)*1000));
        $Start=microtime(true);
        $DoQuery("UPDATE $TableName SET i2=CASE i1\n".implode("\n", $Query)."\nEND\nWHERE i1 IN (".implode(',', range(1, $NumRows)).')');
    }

    print "$TestName: ".(microtime(true)-$Start)."<br>\n";

    file_put_contents("./$TestName.sql", implode(";\n", $TheQueries).';');
}

11
Você está fazendo a obra do Senhor aqui;) Muito apreciado.
chili

Testando algum desempenho entre o GoLang e o PHP, usando 40k linhas no MariaDB, eu estava obtendo 2 segundos no PHP e mais de 6 segundos no golang ... Bem, sempre me disseram que o GoLang funcionaria mais rápido que o PHP !!! Então, eu começo a pensar em como melhorar o desempenho ... Usando o INSERIR ... NA ATUALIZAÇÃO DUPLICATIVA DE CHAVES ... Eu tenho 0,74 s no Golang e 0,86 s no PHP !!!!
Diego Favero

11
O objetivo do meu código é limitar os resultados de temporização estritamente às instruções SQL, e não o código da linguagem ou das bibliotecas. GoLang e PHP são duas linguagens completamente separadas, destinadas a coisas completamente diferentes. O PHP é destinado a um ambiente de script de execução única em um único encadeamento com coleta de lixo principalmente limitada e passiva. O GoLang destina-se a aplicativos compilados de longa execução com coleta de lixo agressiva e multithreading como um dos recursos principais do idioma. Eles mal podiam ser mais diferentes em termos de funcionalidade e razão da linguagem. [Continua]
Dakusan 03/04/19

Portanto, ao executar seus testes, limite as medidas de velocidade para que a função "Consulta" exija estritamente a instrução SQL. Comparar e otimizar as outras partes do código-fonte que não são estritamente a chamada de consulta é como comparar maçãs e laranjas. Se você limitar seus resultados a isso (com as strings pré-compiladas e prontas para usar), os resultados deverão ser muito semelhantes. Quaisquer diferenças nesse ponto são culpa da biblioteca SQL da linguagem e não necessariamente da própria linguagem. Na minha opinião, a solução INSERT ON DUPLICATE foi e sempre será a melhor opção [Cont].
Dakusan

Quanto ao seu comentário sobre o GoLang ser mais rápido, essa é uma afirmação incrivelmente ampla que não leva em conta as inúmeras advertências ou nuances dessas linguagens e de seus designs. Java é uma linguagem interpretada, mas descobri há 15 anos que na verdade quase consegue igualar (e às vezes até bater) C em velocidade em certos cenários. E C é uma linguagem compilada e a mais comum das linguagens de sistema de nível mais baixo, além do assembler. Eu realmente amo o que golang está fazendo e ele definitivamente tem o poder e fluidez para se tornar um dos mais sistemas comuns e otimizado [Cont]
Dakusan

9

Use uma tabela temporária

// Reorder items
function update_items_tempdb(&$items)
{
    shuffle($items);
    $table_name = uniqid('tmp_test_');
    $sql = "CREATE TEMPORARY TABLE `$table_name` ("
        ."  `id` int(10) unsigned NOT NULL AUTO_INCREMENT"
        .", `position` int(10) unsigned NOT NULL"
        .", PRIMARY KEY (`id`)"
        .") ENGINE = MEMORY";
    query($sql);
    $i = 0;
    $sql = '';
    foreach ($items as &$item)
    {
        $item->position = $i++;
        $sql .= ($sql ? ', ' : '')."({$item->id}, {$item->position})";
    }
    if ($sql)
    {
        query("INSERT INTO `$table_name` (id, position) VALUES $sql");
        $sql = "UPDATE `test`, `$table_name` SET `test`.position = `$table_name`.position"
            ." WHERE `$table_name`.id = `test`.id";
        query($sql);
    }
    query("DROP TABLE `$table_name`");
}

8
UPDATE table1, table2 SET table1.col1='value', table2.col1='value' WHERE table1.col3='567' AND table2.col6='567'

Isso deve funcionar para você.

Há uma referência no manual do MySQL para várias tabelas.


6

Por que ninguém menciona várias instruções em uma consulta ?

No php, você usa multi_query método da instância mysqli.

Do manual php

O MySQL opcionalmente permite ter várias instruções em uma sequência de instruções. O envio de várias instruções ao mesmo tempo reduz as viagens de ida e volta do cliente-servidor, mas requer tratamento especial.

Aqui está o resultado comparando com outros 3 métodos na atualização 30.000 bruta. O código pode ser encontrado aqui, com base na resposta de @Dakusan

Transação: 5.5194580554962
Inserção: 0.20669293403625
Caso: 16.474853992462
Multi: 0,0412278175354

Como você pode ver, a consulta de várias instruções é mais eficiente que a resposta mais alta.

Se você receber uma mensagem de erro como esta:

PHP Warning:  Error while sending SET_OPTION packet

Você pode precisar aumentar o max_allowed_packetarquivo de configuração do mysql que está na minha máquina /etc/mysql/my.cnfe depois reiniciar o mysqld.


Todas as comparações abaixo são executadas no teste INSERT. Acabei de executar o teste nas mesmas condições e, sem transações, era 145x mais lento em 300 linhas e 753x mais lento em 3000 linhas. Eu tinha começado originalmente com as 30.000 linhas, mas fui almoçar, voltei e ainda estava acontecendo. Isso faz sentido, pois executar consultas individuais e liberar cada uma delas no banco de dados individualmente seriam ridiculamente caras. Especialmente com replicação. Ativar transações, porém, faz uma grande diferença. Em 3.000 linhas, levou 1,5x mais e em 30.000 linhas, 2,34x . [continuação]
Dakusan 26/11/17

Mas você estava certo sobre ser rápido (com transações). Nas 3.000 e 30.000 linhas, era mais rápido que tudo, exceto o método INSERT. Não há absolutamente nenhuma maneira de obter melhores resultados ao executar 1 consulta do que 30.000 consultas, mesmo que elas sejam agrupadas em lotes em uma chamada especial da API do MySQL. Executando apenas 300 linhas, foi MUITO mais rápido que todos os outros métodos (para minha surpresa), que segue aproximadamente a mesma curva gráfica do método CASE. Isso é mais rápido e pode ser explicado de duas maneiras. A primeira é que o método INSERT sempre insere essencialmente 2 linhas devido a "ON DUPLICATE KEY [cont]
Dakusan

UPDATE ", causando um" INSERT "e" UPDATE ". O outro é que é menos trabalhoso no processador SQL apenas para editar 1 linha por vez devido a pesquisas de índice. Não tenho certeza de como você obteve resultados diferentes dos que eu, mas o seu teste adicional parece sólido Eu não sou realmente ainda certeza de como a replicação iria lidar com esta chamada isso também só funcionam para fazer chamadas ATUALIZAÇÃO chamadas Insira será sempre mais rápido com a consulta INSERT único....
Dakusan

Eu estava fazendo 300 UPDATEs de cada vez em uma tabela para revisar um erro dentro de um loop for que levou 41 segundos. Colocar as mesmas consultas UPDATE em uma $mysqli->multi_query($sql)levou "0" segundos. No entanto, consultas subsequentes falharam, fazendo com que eu fizesse um "programa" separado.
Chris K

Obrigado. Conseguiu atualizar cerca de 5 mil linhas (não testou mais) em um minuto usando várias consultas. Se alguém estiver procurando por uma solução DOP: stackoverflow.com/questions/6346674/…
Scofield

3

Existe uma configuração que você pode alterar, chamada 'multi statement', que desativa o 'mecanismo de segurança' do MySQL implementado para evitar (mais de um) comando de injeção. Típico da implementação "brilhante" do MySQL, também impede que o usuário faça consultas eficientes.

Aqui ( http://dev.mysql.com/doc/refman/5.1/en/mysql-set-server-option.html ) estão algumas informações sobre a implementação C da configuração.

Se você estiver usando PHP, poderá usar o mysqli para executar várias instruções (acho que o php já vem com o mysqli há algum tempo)

$con = new mysqli('localhost','user1','password','my_database');
$query = "Update MyTable SET col1='some value' WHERE id=1 LIMIT 1;";
$query .= "UPDATE MyTable SET col1='other value' WHERE id=2 LIMIT 1;";
//etc
$con->multi_query($query);
$con->close();

Espero que ajude.


4
É o mesmo que enviar as consultas separadamente. A única diferença é que você envia tudo em um pacote de rede, mas as UPDATEs ainda serão processadas como consultas separadas. Melhor é agrupá-los em uma transação, e as alterações serão confirmadas na tabela de uma só vez.
precisa saber é o seguinte

3
Como envolvê-los em uma transação? Mostre-nos, por favor.
TomeeNS

@TomeeNS Use mysqli::begin_transaction(..)antes de enviar a consulta e mysql::commit(..)depois. Ou use START TRANSACTIONcomo primeira e COMMITcomo última instrução na própria consulta.
Juha Palomäki

3

Você pode alternar a mesma tabela para fornecer os IDs pelos quais deseja inserir (se estiver fazendo uma atualização linha por linha:

UPDATE table1 tab1, table1 tab2 -- alias references the same table
SET 
col1 = 1
,col2 = 2
. . . 
WHERE 
tab1.id = tab2.id;

Além disso, deve parecer óbvio que você também pode atualizar de outras tabelas. Nesse caso, a atualização funciona como uma instrução "SELECT", fornecendo os dados da tabela que você está especificando. Você está declarando explicitamente em sua consulta os valores de atualização para que a segunda tabela não seja afetada.


2

Você também pode estar interessado em usar junções nas atualizações, o que também é possível.

Update someTable Set someValue = 4 From someTable s Inner Join anotherTable a on s.id = a.id Where a.id = 4
-- Only updates someValue in someTable who has a foreign key on anotherTable with a value of 4.

Editar: se os valores que você está atualizando não vierem de outro lugar no banco de dados, será necessário emitir várias consultas de atualização.


1

E agora o caminho mais fácil

update speed m,
    (select 1 as id, 20 as speed union
     select 2 as id, 30 as speed union
     select 99 as id, 10 as speed
        ) t
set m.speed = t.speed where t.id=m.id

-1

usar

REPLACE INTO`table` VALUES (`id`,`col1`,`col2`) VALUES
(1,6,1),(2,2,3),(3,9,5),(4,16,8);

Observe:

  • id deve ser uma chave exclusiva primária
  • se você usar chaves estrangeiras para fazer referência à tabela, REPLACE exclui e insere, portanto, isso pode causar um erro

-3

O seguinte atualizará todas as linhas em uma tabela

Update Table Set
Column1 = 'New Value'

O próximo atualizará todas as linhas em que o valor da Coluna2 é superior a 5

Update Table Set
Column1 = 'New Value'
Where
Column2 > 5

Há todo o exemplo da Unkwntech de atualizar mais de uma tabela

UPDATE table1, table2 SET
table1.col1 = 'value',
table2.col1 = 'value'
WHERE
table1.col3 = '567'
AND table2.col6='567'

-3

Sim .. é possível usando a instrução sql INSERT ON DUPLICATE KEY UPDATE .. sintaxe: INSERT INTO table_name (a, b, c) VALUES (1,2,3), (4,5,6) ON DUPLICATE KEY UPDATE a = VALORES (a), b = VALORES (b), c = VALORES (c)


-5
UPDATE tableName SET col1='000' WHERE id='3' OR id='5'

Isso deve alcançar o que você está procurando. Basta adicionar mais IDs. Eu testei.


-7
UPDATE `your_table` SET 

`something` = IF(`id`="1","new_value1",`something`), `smth2` = IF(`id`="1", "nv1",`smth2`),
`something` = IF(`id`="2","new_value2",`something`), `smth2` = IF(`id`="2", "nv2",`smth2`),
`something` = IF(`id`="4","new_value3",`something`), `smth2` = IF(`id`="4", "nv3",`smth2`),
`something` = IF(`id`="6","new_value4",`something`), `smth2` = IF(`id`="6", "nv4",`smth2`),
`something` = IF(`id`="3","new_value5",`something`), `smth2` = IF(`id`="3", "nv5",`smth2`),
`something` = IF(`id`="5","new_value6",`something`), `smth2` = IF(`id`="5", "nv6",`smth2`) 

// Você apenas constrói em php como

$q = 'UPDATE `your_table` SET ';

foreach($data as $dat){

  $q .= '

       `something` = IF(`id`="'.$dat->id.'","'.$dat->value.'",`something`), 
       `smth2` = IF(`id`="'.$dat->id.'", "'.$dat->value2.'",`smth2`),';

}

$q = substr($q,0,-1);

Então você pode atualizar a tabela de furos com uma consulta


Eu não downvote, mas acho que a objeção é a de fazer o set, quando não é necessário (e você ainda está fazendo isso, quando você está definindo somethinga something)
v010dya
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.