Несколько обновлений в MySQL - PullRequest
       100

Несколько обновлений в MySQL

353 голосов
/ 06 августа 2008

Я знаю, что вы можете вставить несколько строк одновременно, есть ли способ обновить несколько строк одновременно (как в одном запросе) в MySQL?

Edit: Например, у меня есть следующее

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

Я хочу объединить все следующие обновления в один запрос

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;

Ответы [ 17 ]

593 голосов
/ 06 августа 2008

Да, это возможно - вы можете использовать INSERT ... ON DUPLICATE KEY UPDATE.

Используя ваш пример:

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);
114 голосов
/ 17 сентября 2008

Поскольку у вас есть динамические значения, вам нужно использовать IF или CASE для обновляемых столбцов. Это немного уродливо, но должно работать.

Используя ваш пример, вы можете сделать это следующим образом:

UPDATE table SET Col1 = CASE id 
                          WHEN 1 THEN 1 
                          WHEN 2 THEN 2 
                          WHEN 4 THEN 10 
                          ELSE Col1 
                        END, 
                 Col2 = CASE id 
                          WHEN 3 THEN 3 
                          WHEN 4 THEN 12 
                          ELSE Col2 
                        END
             WHERE id IN (1, 2, 3, 4);
82 голосов
/ 25 июня 2013

Вопрос старый, но я бы хотел расширить тему другим ответом.

Суть в том, что самый простой способ достичь этого - просто заключить несколько запросов в транзакцию. Принятый ответ INSERT ... ON DUPLICATE KEY UPDATE - хороший взлом, но нужно помнить о его недостатках и ограничениях:

  • Как уже говорилось, если вам случится запустить запрос со строками, первичные ключи которых не существуют в таблице, запрос вставит новые "недоделанные" записи. Вероятно, это не то, что вы хотите
  • Если у вас есть таблица с ненулевым полем без значения по умолчанию и вы не хотите прикасаться к этому полю в запросе, вы получите "Field 'fieldname' doesn't have a default value" предупреждение MySQL, даже если вы вообще не вставляете ни одной строки , Это может привести к неприятностям, если вы решите быть строгим и превратите предупреждения mysql в исключения времени выполнения в вашем приложении.

Я провел несколько тестов производительности для трех предложенных вариантов, включая вариант INSERT ... ON DUPLICATE KEY UPDATE, вариант с предложением «case / when / then» и наивный подход с транзакцией. Вы можете получить код Python и результаты здесь . Общий вывод заключается в том, что вариант с оператором case оказывается в два раза быстрее, чем два других варианта, но написать для него правильный и безопасный для инъекций код довольно сложно, поэтому я лично придерживаюсь самого простого подхода: использование транзакций.

Редактировать: Результаты Дакусан доказывают, что мои оценки производительности не совсем верны. Пожалуйста, смотрите этот ответ для другого, более детального исследования.

54 голосов
/ 26 сентября 2013

Не уверен, почему еще одна полезная опция еще не упомянута:

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;
32 голосов
/ 03 октября 2016

Все следующее относится к InnoDB.

Я чувствую, что знание скорости 3 различных методов важно.

Есть 3 метода:

  1. ВСТАВИТЬ: ВСТАВИТЬ с ДУБЛИКАТНЫМ КЛЮЧОМ ОБНОВЛЕНИЕ
  2. СДЕЛКА: где вы делаете обновление для каждой записи в транзакции
  3. СЛУЧАЙ: В каком случае вы / когда для каждой отдельной записи в ОБНОВЛЕНИИ

Я только что проверил это, и метод INSERT был для меня 6,7x быстрее, чем метод TRANSACTION. Я примерил набор из 3000 и 30000 рядов.

Метод TRANSACTION по-прежнему должен запускать каждый отдельный запрос, который занимает время, хотя он выполняет пакетирование результатов в памяти или что-то во время выполнения. Метод TRANSACTION также довольно дорог в журналах репликации и запросов.

Еще хуже, метод CASE был 41,1x медленнее, чем метод INSERT с 30 000 записей (в 6,1 раза медленнее, чем TRANSACTION). И 75x медленнее в MyISAM. Методы INSERT и CASE не превышают 1000 записей. Даже при 100 записях метод CASE БЫСТРО быстрее.

Итак, я считаю, что метод INSERT - лучший и самый простой в использовании. Запросы меньше и их легче читать, и они занимают только 1 запрос действия. Это относится как к InnoDB, так и к MyISAM.

Бонусные вещи:

Решением проблемы INSERT, не являющейся полем по умолчанию, является временное отключение соответствующих режимов SQL: SET SESSION sql_mode=REPLACE(REPLACE(@@SESSION.sql_mode,"STRICT_TRANS_TA‌​BLES",""),"STRICT_AL‌​L_TABLES",""). Обязательно сначала сохраните sql_mode, если планируете его вернуть.

Что касается других комментариев, я видел, что auto_increment увеличивается с использованием метода INSERT, я тоже это проверял, и, похоже, это не так.

Код для запуска тестов следующий. Он также выводит файлы .SQL для удаления накладных расходов интерпретатора 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).';');
}
8 голосов
/ 07 апреля 2011

Использовать временную таблицу

// 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 голосов
/ 06 августа 2008
UPDATE table1, table2 SET table1.col1='value', table2.col1='value' WHERE table1.col3='567' AND table2.col6='567'

Это должно сработать для тебя.

В руководстве по MySQL есть ссылка для нескольких таблиц

.
3 голосов
/ 05 июля 2017

Почему никто не упоминает несколько операторов в одном запросе ?

В php вы используете multi_query метод экземпляра mysqli.

Из руководства php

MySQL дополнительно позволяет иметь несколько операторов в одной строке операторов. Одновременная отправка нескольких операторов сокращает количество циклов между клиентом и сервером, но требует особой обработки.

Вот результат по сравнению с другими 3 методами в обновлении 30000 raw. Код можно найти здесь , основанный на ответе @Dakusan

Транзакция: 5,5194580554962
Вставка: 0,20669293403625
Дело: 16.474853992462
Мульти: 0,0412278175354

Как видите, запрос с несколькими утверждениями более эффективен, чем самый высокий ответ.

Если вы получите сообщение об ошибке, подобное этому:

PHP Warning:  Error while sending SET_OPTION packet

Вам может потребоваться увеличить max_allowed_packet в файле конфигурации mysql, который на моем компьютере равен /etc/mysql/my.cnf, а затем перезапустить mysqld.

3 голосов
/ 02 января 2013

Вы можете создать псевдоним той же таблицы, чтобы получить идентификаторы, по которым вы хотите вставить (если вы обновляете строку за строкой:

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

Кроме того, должно показаться очевидным, что вы также можете обновлять данные из других таблиц. В этом случае обновление удваивается как оператор «SELECT», предоставляя вам данные из указанной вами таблицы. Вы явно указываете в своем запросе значения обновлений, поэтому вторая таблица не затрагивается.

2 голосов
/ 06 августа 2008

Вас также может заинтересовать использование объединений в обновлениях, что также возможно.

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.

Редактировать: Если обновляемые значения не поступают откуда-то еще из базы данных, вам нужно будет выполнить несколько запросов на обновление.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...