Множественные обновления в MySQL

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

Редактировать: Например, у меня есть следующие

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;
Освоение архитектуры микросервисов с Laravel: Лучшие практики, преимущества и советы для разработчиков
Освоение архитектуры микросервисов с Laravel: Лучшие практики, преимущества и советы для разработчиков
В последние годы архитектура микросервисов приобрела популярность как способ построения масштабируемых и гибких приложений. Laravel , популярный PHP...
Как построить CRUD-приложение в Laravel
Как построить CRUD-приложение в Laravel
Laravel - это популярный PHP-фреймворк, который позволяет быстро и легко создавать веб-приложения. Одной из наиболее распространенных задач в...
Освоение PHP и управление базами данных: Создание собственной СУБД - часть II
Освоение PHP и управление базами данных: Создание собственной СУБД - часть II
В предыдущем посте мы создали функциональность вставки и чтения для нашей динамической СУБД. В этом посте мы собираемся реализовать функции обновления...
Документирование API с помощью Swagger на Springboot
Документирование API с помощью Swagger на Springboot
В предыдущей статье мы уже узнали, как создать Rest API с помощью Springboot и MySql .
Роли и разрешения пользователей без пакета Laravel 9
Роли и разрешения пользователей без пакета Laravel 9
Этот пост изначально был опубликован на techsolutionstuff.com .
Как установить LAMP Stack - Security 5/5 на виртуальную машину Azure Linux VM
Как установить LAMP Stack - Security 5/5 на виртуальную машину Azure Linux VM
В предыдущей статье мы завершили установку базы данных, для тех, кто не знает.
417
0
418 498
18
Перейти к ответу Данный вопрос помечен как решенный

Ответы 18

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

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

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

Следующее обновит все строки в одной таблице

Update Table Set
Column1 = 'New Value'

Следующий обновит все строки, в которых значение Column2 больше 5.

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

Есть все примеры Unkwntech обновления более чем одной таблицы

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

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

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.

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

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

Это должно достичь того, что вы ищете. Просто добавьте еще идентификаторы. Я это проверил.

Ответ принят как подходящий

Да, это возможно - вы можете использовать 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);

Если нет дубликатов, я не хочу, чтобы эта строка была вставлена. что должен делать id? потому что я получаю информацию с другого сайта, который поддерживает таблицы с идентификаторами. Я вставляю значения по этому идентификатору. если на сайте есть новые записи, я вставлю только идентификаторы и подсчет, за исключением всей другой информации. если и только если есть запись для идентификатора, он должен обновиться, иначе он должен пропустить. что мне делать?

Jayapal Chandran 30.08.2010 17:30

так что, если это похоже на вставку в пропустить null или пропустить пустой при дублировании обновления ... тогда это было бы хорошо.

Jayapal Chandran 30.08.2010 17:34

Примечание: этот ответ также предполагает, что идентификатор является первичным ключом.

JM4 12.12.2012 22:01

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

strannik 04.09.2013 16:59

@JayapalChandran, вы должны использовать INSERT IGNORE вместе с ON DUPLICATE KEY UPDATE. dev.mysql.com/doc/refman/5.5/en/insert.html

Haralan Dobrev 16.09.2013 18:15

@HaralanDobrev Использование INSERT IGNORE по-прежнему вставляет недублированные записи. чего Джаяпал хотел избежать. INSERT IGNORE просто превращает любые ошибки в предупреждение :( stackoverflow.com/questions/548541/…

Takehiro Adachi 24.10.2013 21:32

@ JM4: этот ответ предполагает, что идентификатор является первичным ключом ИЛИ уникальным ключом (но, конечно, наиболее вероятно, что первичный ключ)

Walter Tross 08.02.2014 02:32

Этот ответ предполагает, что идентификатор является уникальным ключом (может быть первичным, как говорили другие), но, что более важно, он предполагает, что других уникальных ключей нет. Если они есть, это может помешать работе.

Steve Horvath 03.12.2014 08:43

Вы также должны позаботиться о своем приращении (если он существует), он ТАКЖЕ увеличится, если вы вставите O.D.K.U. даже если новая запись не вставлена! Если вы будете делать много обновлений таким образом, ваш автоинкремент так скоро переполнится!

user2602807 05.03.2015 14:51

Мое решение проблемы только обновления, но не вставки новых записей состоит в том, чтобы иметь поле в части вставки, которое отсутствует в части обновления, и после того, как я удалю все записи, которые имеют значения в этом поле. пример: вставить в tbl (идентификатор, заголовок, обходной путь) значения ('realId', 'realTitle', 'Эта строка должна умереть!') при обновлении повторяющегося ключа id = values ​​(id), title = values ​​(title); удалить из таблицы, где обходной путь = 'Эта строка должна умереть!'

santiago arizti 08.08.2016 22:13

@ user2602807 Только что протестировал, и похоже, что это не так

Dakusan 03.10.2016 14:06

Еще одно предостережение: любые дополнительные столбцы, объявленные как NOT NULL без значения по умолчанию, должны быть включены во вставку. Однако их можно игнорировать в обновлении, поэтому вы можете использовать фиктивные значения.

shmosel 12.01.2017 13:24

Я получаю "синтаксическую ошибку 1064". Формат для DUPLICATE KEY UPDATE Col1=VALUES(Col1) colName1 = VALUES (colName1)? т.е. где `будет окружать colName1

Phillip 27.01.2018 23:35

Обратите внимание, что я также пытался без `окружающего colName1 ... в настоящее время тестирую, чтобы убедиться, что это не данные конкретной строки, вызывающие ошибку

Phillip 28.01.2018 20:37

Я разместил вопрос по проблеме: stackoverflow.com/questions/48490630/…

Phillip 28.01.2018 23:58

Это не идеальное решение. Вот намного лучше stackoverflow.com/questions/25674737/…

Serhii Smirnov 03.07.2019 12:50

user2602807 @Dakusan, вы оба правы, в зависимости от двух вещей: 1. myisam не увеличивает автоинкремент. 2. innodb увеличивается по умолчанию, вы можете предотвратить это, изменив режим автоинкремента на классический.

Ömer An 15.05.2020 12:03

@ Омер Ан - Да. Я обновил свой ответ ниже этой информацией некоторое время назад. К сожалению, переполнение стека не позволяет мне редактировать мой вышеупомянутый комментарий.

Dakusan 16.05.2020 15:05

Поскольку у вас есть динамические значения, вам нужно использовать 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);

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

me_ 08.02.2017 00:55

@ user2536953, может быть неплохо и для динамического обновления. Например, я использовал это решение в цикле на 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 15.02.2017 11:43

Существует параметр, который вы можете изменить, называемый «multi statement», который отключает «механизм безопасности» MySQL, реализованный для предотвращения (более чем одной) команды внедрения. Типичный для «блестящей» реализации MySQL, он также мешает пользователю выполнять эффективные запросы.

Здесь (http://dev.mysql.com/doc/refman/5.1/en/mysql-set-server-option.html) - некоторая информация о реализации этой настройки на языке C.

Если вы используете PHP, вы можете использовать mysqli для выполнения нескольких операторов (я думаю, что php уже некоторое время поставляется с mysqli)

$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();

Надеюсь, это поможет.

Это то же самое, что и отправка запросов по отдельности. Единственное отличие состоит в том, что вы отправляете все это в одном сетевом пакете, но ОБНОВЛЕНИЯ по-прежнему будут обрабатываться как отдельные запросы. Лучше заключить их в одну транзакцию, тогда изменения сразу будут зафиксированы в таблице.

Marki555 12.04.2014 19:01

Как их завернуть в одну транзакцию? Покажи нам, пожалуйста.

TomeeNS 22.09.2014 22:20

@TomeeNS Используйте mysqli::begin_transaction(..) перед отправкой запроса и mysql::commit(..) после. Или используйте START TRANSACTION в качестве первого и COMMIT в качестве последнего оператора в самом запросе.

Juha Palomäki 16.11.2016 02:55

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

// 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`");
}

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

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

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

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

Я хочу сказать, что самый простой способ добиться этого - просто обернуть транзакцией несколько запросов. Принятый ответ 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 оказывается в два раза быстрее, чем два других варианта, но для него довольно сложно написать правильный и безопасный для инъекций код, поэтому я лично придерживаюсь самого простого подхода: использования транзакций.

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

Использование транзакций, очень приятный (и простой) совет!

mTorres 30.07.2014 21:24

Что делать, если мои таблицы не относятся к типу InnoDB?

TomeeNS 22.09.2014 22:26

Может ли кто-нибудь предоставить ссылку на то, как это выглядит? И / или код для безопасного для инъекций кода для варианта с оператором case?

François M. 21.07.2015 19:44

Я считаю, что информация о скорости в этом посте неверна. Я написал об этом в посте ниже. stackoverflow.com/questions/3432/multiple-updates-in-mysql/…

Dakusan 03.10.2016 15:07

@ Дакусан, отличный ответ. Большое спасибо за расширение, комментирование и исправление моих результатов.

Roman Imankulov 04.10.2016 10:04

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`) 

// Вы просто создаете его на php, например

$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);

Таким образом, вы можете обновить таблицу отверстий одним запросом

Я не голосовал против, но я думаю, что возражение заключается в том, чтобы делать набор, когда он не нужен (и вы все еще делаете это, когда вы устанавливаете something на something)

v010dya 30.12.2014 12:04

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

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;

Это лучшее. Особенно, если вы извлекаете значения для обновления из другого SQL-запроса, как это делал я.

v010dya 30.12.2014 11:55

Это было отличным вариантом для обновления таблицы с огромным количеством столбцов. Я, вероятно, буду часто использовать этот запрос в будущем. Спасибо!

Casper Wilkes 09.01.2015 23:18

Я пробовал этот тип запроса. Но когда записи достигают 30к, пограничный сервер останавливается. Есть ли другое решение?

Bhavin Chauhan 27.09.2016 09:27

Выглядит отлично. Я попытаюсь объединить это с предложением WHERE, в котором первичные ключи не обновляются, а используются для идентификации столбцов, которые нужно изменить.

nl-x 02.03.2020 16:30

@BhavinChauhan Пробовали ли вы использовать временную таблицу вместо join-select, чтобы обойти проблему?

nl-x 02.03.2020 16:32

Я обнаружил тот же подход, когда пытался избежать [явных] временных таблиц. @BhavinChauhan, вам всегда следует избегать слишком длинных строк запроса. Вместо этого смешайте их. По моим (ограниченным) измерениям 2500 строк были идеальным размером партии.

Timo 26.05.2020 12:48

Да .. это возможно с помощью инструкции sql INSERT ON DUPLICATE KEY UPDATE .. синтаксис: INSERT INTO имя_таблицы (a, b, c) VALUES (1,2,3), (4,5,6) ПРИ ДВОЙНОМ ОБНОВЛЕНИИ КЛЮЧА a = ЗНАЧЕНИЯ (a), b = ЗНАЧЕНИЯ (b), c = ЗНАЧЕНИЯ (c)

использовать

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

Пожалуйста, обрати внимание:

  • id должен быть первичным уникальным ключом
  • если вы используете внешние ключи для ссылка на таблицу, REPLACE удаляет, затем вставляет, поэтому это может вызвать ошибку

Все следующее применимо к InnoDB.

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

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

  1. INSERT: INSERT с ON DUPLICATE KEY UPDATE
  2. ТРАНЗАКЦИЯ: когда вы обновляете каждую запись в транзакции.
  3. СЛУЧАЙ: в котором вы случай / когда для каждой отдельной записи в ОБНОВЛЕНИИ

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

Метод 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_TABLES",""),"STRICT_ALL_TABLES",""). Обязательно сначала сохраните sql_mode, если вы планируете вернуть его обратно.

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

Код для запуска тестов следующий. Он также выводит файлы .SQL для устранения накладных расходов интерпретатора php.

<?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).';');
}

Вы делаете здесь работу ГОСПОДА;) Очень признателен.

chili 29.01.2018 15:14

Тестируя некоторую производительность между GoLang и PHP, используя 40k строк на MariaDB, я получал 2 секунды на PHP и более 6 секунд на golang .... Ну, мне всегда говорили, что GoLang будет работать быстрее, чем PHP !!! Итак, я начинаю задаваться вопросом, как улучшить производительность ... Используя INSERT ... ON DUPLICATE KEY UPDATE ... У меня 0,74 секунды на Golang и 0,86 секунды на PHP !!!!

Diego Favero 02.04.2019 21:13

Суть моего кода в том, чтобы ограничить результаты по времени строго операторами SQL, а не кодом для языка или библиотек. GoLang и PHP - это два совершенно разных языка, предназначенных для совершенно разных вещей. PHP предназначен для одноразовой среды сценариев в одном потоке с в основном ограниченной и пассивной сборкой мусора. GoLang предназначен для долго работающих скомпилированных приложений с агрессивной сборкой мусора и многопоточностью в качестве одной из основных функций языка. Они едва ли могли быть более разными с точки зрения языковой функциональности и разума. [Продолжение]

Dakusan 03.04.2019 06:35

Поэтому при выполнении тестов убедитесь, что измерения скорости строго ограничены вызовами функции «Запрос» для оператора SQL. Сравнение и оптимизация других частей исходного кода, которые не являются строго вызовом запроса, похожи на сравнение яблок и апельсинов. Если вы ограничите свои результаты этим (предварительно скомпилированные и готовые к работе строки), то результаты должны быть очень похожими. Любые различия на этом этапе являются ошибкой библиотеки SQL языка, а не обязательно самого языка. На мой взгляд, решение INSERT ON DUPLICATE было и всегда будет лучшим вариантом. [Продолжение]

Dakusan 03.04.2019 06:41

Что касается вашего комментария о том, что GoLang работает быстрее, это невероятно широкое утверждение, которое не принимает во внимание ни одно из множества предостережений или нюансов этих языков и их дизайна. Java - это интерпретируемый язык, но 15 лет назад я обнаружил, что он фактически может почти соответствовать (а может быть, даже иногда превосходить) C по скорости в определенных сценариях. А C - это компилируемый язык и самый распространенный из системных языков нижнего уровня, помимо ассемблера. Мне очень нравится то, что делает GoLang, и он определенно обладает мощностью и гибкостью, чтобы стать одной из наиболее распространенных и оптимизированных систем [Продолжение]

Dakusan 03.04.2019 06:46

языки приложений. Он компилируется намного быстрее по сравнению с C / C++. Он имеет строгий синтаксис, который помогает устранить войны за форматирование кода (хотя лично мне это не нравится). Его сборка мусора является революционной, с обычными запусками GC, которые занимают МИКРОСЕКУНД. И простота его формата канала IPC почти не имеет себе равных. Это довольно революционно, для чего он построен. Например, запуск пустого цикла for и сравнение скоростей между языками мало что вам скажет. Хорошо, я уже набрал слишком много и не позволю этому столкнуться с другим комментарием.

Dakusan 03.04.2019 06:50

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

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

От руководство по php

MySQL optionally allows having multiple statements in one statement string. Sending multiple statements at once reduces client-server round trips but requires special handling.

Вот результат по сравнению с другими 3 методами в обновлении 30,000 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.

Все нижеприведенные сравнения сравниваются с тестом INSERT. Я просто провел тест в тех же условиях, и без транзакций он был медленнее на 145x на 300 строках и 753x на 3000 строках. Изначально я начал с 30 000 строк, но пошел приготовить себе обед, а потом вернулся, и он все еще продолжался. Это имеет смысл, поскольку выполнение отдельных запросов и сброс каждого из них в базу данных по отдельности было бы смехотворно дорогим. Особенно с репликацией. Однако включение транзакций имеет большое значение. При 3000 строках потребовалось больше В 1,5 раза, а при 30 000 строках - В 2,34 раза. [продолжение]

Dakusan 27.11.2017 01:04

Но вы были правы, что это было быстро (с транзакциями). И для 3000, и для 30 000 строк это было быстрее, чем все, кроме метода INSERT. Нет абсолютно никакого способа получить лучшие результаты от выполнения 1 запроса, чем 30 000 запросов, даже если они объединены в специальный вызов MySQL API. Выполняя всего 300 строк, он был НАМНОГО быстрее, чем все другие методы (к моему удивлению), который следует примерно по той же кривой графика, что и метод CASE. Это можно объяснить двумя способами. Во-первых, метод INSERT по существу всегда вставляет 2 строки из-за «ON DUPLICATE KEY [продолжение]

Dakusan 27.11.2017 01:18

UPDATE "вызывает как" INSERT ", так и" UPDATE ". Другое дело, что в процессоре SQL меньше работы для редактирования только одной строки за раз из-за поиска по индексу. Я не уверен, как вы получили другие результаты, чем я, но ваш дополнительный тест выглядит надежным. На самом деле я даже не уверен, как репликация будет обрабатывать этот вызов. Это также будет работать только для выполнения вызовов UPDATE. Вызовы вставки ВСЕГДА будут самыми быстрыми с одним запросом INSERT.

Dakusan 27.11.2017 01:21

Я делал 300 ОБНОВЛЕНИЙ за раз для таблицы, чтобы исправить ошибку в цикле for, который занял 41 секунду. Помещение тех же запросов UPDATE в один $mysqli->multi_query($sql) заняло «0» секунд. Однако последующие запросы не удались, из-за чего я выделил эту «программу».

Chris K 11.01.2018 21:30

Спасибо. Смог обновить около 5 тыс. Строк (больше не тестировал) за минуту, используя несколько запросов. Если кто-то ищет решение PDO: stackoverflow.com/questions/6346674/…

Scofield 20.02.2019 22:20

А теперь легкий путь

update my_table m, -- let create a temp table with populated values
    (select 1 as id, 20 as value union -- this part will be generated
     select 2 as id, 30 as value union -- using a backend code
     -- for loop 
     select N as id, X as value
        ) t
set m.value = t.value where t.id=m.id -- now update by join - quick

Я взял ответ от @newtover и расширил его, используя новую функцию json_table в MySql 8. Это позволяет вам создать хранимую процедуру для обработки рабочей нагрузки, а не создавать собственный текст SQL в коде:

drop table if exists `test`;
create table `test` (
  `Id` int,
  `Number` int,
  PRIMARY KEY (`Id`)
);
insert into test (Id, Number) values (1, 1), (2, 2);

DROP procedure IF EXISTS `Test`;
DELIMITER $$
CREATE PROCEDURE `Test`(
    p_json json
)
BEGIN
    update test s
        join json_table(p_json, '$[*]' columns(`id` int path '$.id', `number` int path '$.number')) v 
        on s.Id=v.id set s.Number=v.number;
END$$
DELIMITER ;

call `Test`('[{"id": 1, "number": 10}, {"id": 2, "number": 20}]');
select * from test;

drop table if exists `test`;

Это на несколько мс медленнее, чем чистый SQL, но я счастлив принять удар, а не генерировать текст sql в коде. Не уверен, насколько он эффективен с огромными наборами записей (объект JSON имеет максимальный размер 1 ГБ), но я использую его все время при обновлении 10 тыс. Строк за раз.

Другие вопросы по теме