Как я могу вставить много строк в таблицу MySQL и вернуть новые идентификаторы?

Обычно я могу вставить строку в таблицу MySQL и вернуть last_insert_id. Однако теперь я хочу массово вставить в таблицу много строк и получить обратно массив идентификаторов. Кто-нибудь знает, как я могу это сделать?

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

Могу ли я получить lastInsertId из массовой вставки?

Оператор вставки-выбора нескольких строк Mysql с last_insert_id () < / а>


person Peacemoon    schedule 07.09.2011    source источник
comment
Почему вы не можете вставить их по одному?   -  person sanmai    schedule 07.09.2011
comment
Вам нужно смоделировать предложение OUTPUT. Я считаю, что вы можете сделать это с помощью триггера в MySQL   -  person Martin Smith    schedule 07.09.2011
comment
Вы не можете использовать массовую вставку, а затем функцию Lime last_insert_id (), это не работает.   -  person N.B.    schedule 07.09.2011
comment
возможный дубликат предложения OUTPUT в mysql   -  person Martin Smith    schedule 07.09.2011
comment
@Martin: На самом деле это не дубликат. Речь идет о получении множества идентификаторов (last_insert_id()), а не одного   -  person Lukas Eder    schedule 07.09.2011
comment
@Peacemoon: Где тебе эти удостоверения личности? В JDBC?   -  person Lukas Eder    schedule 07.09.2011
comment
@ Лукас - А, ладно. В SQL Server триггер может сделать SELECT * FROM INSERTED, чтобы вернуть новые идентификаторы клиенту. Тогда разве это невозможно в MySQL?   -  person Martin Smith    schedule 07.09.2011
comment
@Martin: На самом деле, мне очень любопытно ответить на этот вопрос. В Postgres вы можете написать INSERT .. RETURNING *, чтобы вернуть все только что вставленные строки. MySQL не имеет такого предложения. С помощью JDBC вы почти всегда можете получить все автоматически сгенерированные идентификаторы, но здесь не обязательно речь идет о JDBC. Подождем и посмотрим   -  person Lukas Eder    schedule 07.09.2011
comment
@Eder: я хочу использовать его в PHP. Прямо сейчас я использую хак, вызывая last_insert_id () и row_count (), затем я создаю массив с диапазоном элементов от last_insert_id () до last_insert_id () + row_count (). Не знаю, элегантное ли это решение.   -  person Peacemoon    schedule 08.09.2011
comment
Нет - этот метод тоже ошибочен - если вы не заключите вставку в LOCK TABLES ... WRITE, и вы также должны разрешить auto_increment_increment   -  person symcbean    schedule 09.09.2011


Ответы (9)


Старый поток, но только что изучил это, так что вот оно: если вы используете InnoDB в последней версии MySQL, вы можете получить список идентификаторов, используя LAST_INSERT_ID() и ROW_COUNT().

InnoDB гарантирует последовательные номера для АВТОМАТИЧЕСКОГО УВЕЛИЧЕНИЯ при выполнении массовых вставок, при условии, что для innodb_autoinc_lock_mode установлено значение 0 (традиционный) или 1 (последовательный). Следовательно, вы можете получить первый идентификатор из LAST_INSERT_ID() и последний, добавив ROW_COUNT()-1.

person Dag Sondre Hansen    schedule 16.05.2013
comment
Вам нужна сделка? Что, если бы посередине оказались другие вставки? - person ; 17.07.2013
comment
Пока вы используете движок InnoDB и гарантируете, что блокировка auto_increment включена (что может привести к снижению производительности), явное определение транзакции не требуется. Поскольку InnoDB выполняет блокировку во время вставки, другие вставки должны ждать завершения вставки. Взгляните на dev.mysql.com/ doc / refman / 5.1 / en / - person Dag Sondre Hansen; 29.07.2013
comment
Насколько мне известно, это невозможно сделать для таблиц MyISAM. - person Dag Sondre Hansen; 07.02.2015
comment
Почему нет? Если у вас есть автоматическое приращение для таблиц MyISAM, что мешает делать то же самое? Автоинкремент для MyISAM тоже последовательный, не так ли? Вы имеете в виду тот факт, что вставки могут происходить посередине между массовой вставкой и запуском last_insert_id () и row_count ()? - person CMCDragonkai; 09.02.2015
comment
MyISAM не является транзакционным механизмом базы данных, поэтому во время выполнения массовой вставки могут происходить другие вставки, нарушая последовательность. Блокировка может предотвратить это (хотя и не слишком уверен), но я нашел одно решение, задокументированное MySQL, - это решение, описанное выше. - person Dag Sondre Hansen; 10.02.2015
comment
Что делать, если innodb_autoinc_lock_mode установлено в 2 (режим блокировки с чередованием) и вы выполняете массовую вставку внутри транзакции. Может быть, когда несколько серверов делают это одновременно, insertIds из одной массовой вставки не является последовательным? - person Wulf; 30.06.2016
comment
@Wulf Из документации MySQL о чередующемся режиме: в этом режиме блокировки никакие «INSERT-подобные» операторы не используют блокировку AUTO-INC на уровне таблицы, и несколько операторов могут выполняться одновременно. Это самый быстрый и масштабируемый режим блокировки, но он небезопасен при использовании сценариев репликации на основе операторов или восстановления, когда операторы SQL воспроизводятся из двоичного журнала. - person Dag Sondre Hansen; 30.06.2016
comment
@Dag Sondre Hansen: Но что означают утверждения, подобные INSERT? Если только операторы INSERT создают значения автоинкремента, безопасно ли это использовать? ты! - person Wulf; 30.06.2016
comment
@Wulf Я предполагаю (предполагаю, что он подчеркнут), это относится к INSERT, LOAD DATA, REPLACE INTO и т. Д. - person Dag Sondre Hansen; 30.06.2016
comment
что, если используется ОБНОВЛЕНИЕ ДВОЙНОГО КЛЮЧА? Есть ли способ получить вставленные / обновленные идентификаторы, которые автоматически увеличиваются в случае вставки, но не в случае обновления? - person StackExploded; 18.07.2016
comment
Я бы побоялся использовать это, если одна из строк выйдет из строя. Тогда обработка вставленных идентификаторов обратно в ваш код не будет соответствовать вашему исходному массиву - person NaturalBornCamper; 11.10.2016
comment
почему -1 в row_count ()? - person Adders; 22.04.2017
comment
@Adders Это простая математика: LAST_INSERT_ID содержит первый идентификатор. Если, например, возвращается только одна строка, первое значение - LAST_INSERT_ID, а последнее - LAST_INSERT_ID + 1 - 1 (тот же идентификатор). - person Dag Sondre Hansen; 25.04.2017
comment
Я думаю, также важно отметить, что если вы полагаетесь на такой метод для получения нескольких идентификаторов вставки, вы не сможете использовать кластеры с несколькими ведущими, такие как Galera Cluster, если вам когда-нибудь понадобится такая вещь. - person JohnK; 03.04.2020

Единственный способ, которым я могу это сделать, - это если вы сохраните уникальный идентификатор для каждого набора вставленных строк (guid), а затем выберите идентификаторы строк. например:

INSERT INTO t1
(SELECT col1,col2,col3,'3aee88e2-a981-1027-a396-84f02afe7c70' FROM a_very_large_table);
COMMIT;

SELECT id FROM t1 
WHERE guid='3aee88e2-a981-1027-a396-84f02afe7c70';

Вы также можете сгенерировать guid в базе данных, используя uuid()

person Kevin Burton    schedule 07.09.2011

Предположим, у нас есть таблица с именем temptable с двумя столбцами uid, col1, где uid - поле с автоматическим приращением. Выполнение чего-то подобного ниже вернет все вставленные идентификаторы в наборе результатов. Вы можете просмотреть набор результатов и получить свой идентификатор. Я понимаю, что это старый пост, и это решение может работать не для всех случаев. Но для других это возможно, и поэтому я отвечаю на это.

# lock the table
lock tables temptable write;

#bulk insert the rows;
insert into temptable(col1) values(1),(2),(3),(4);

#get the value of first inserted row. when bulk inserting last_insert_id() #should give the value of first inserted row from bulk op.
set @first_id = last_insert_id();

#now select the auto increment field whose value is greater than equal to #the first row. Remember since you have write lock on that table other #sessions can't write to it. This resultset should have all the inserted #id's
select uid from temptable where uid >=@first_id;

#now that you are done don't forget to unlock the table.
unlock tables;
person sundeep    schedule 11.06.2015

Стоит отметить, что ответ @Dag Sondre Hansen также можно реализовать, если у вас innodb_autoinc_lock_mode установлено значение 2, просто заблокировав таблицу перед вставкой.

LOCK TABLE my_table WRITE;
INSERT INTO my_table (col_a, col_b, col_c) VALUES (1,2,3), (4,5,6), (7,8,9);
SET @row_count = ROW_COUNT();
SET @last_insert_id = LAST_INSERT_ID();
UNLOCK TABLES;
SELECT id FROM my_table WHERE id >= @last_insert_id AND id <= @last_insert_id + (@row_count - 1);

Вот скрипка, демонстрирующая: https://www.db-fiddle.com/f/ahXAhosYkkRmwqR9Y4mAsr/0

person billynoah    schedule 02.02.2021

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

Один из способов сделать это, который может сработать, если все ваши вставки выполнены успешно (!), Заключается в следующем:

Затем вы можете получить вставленные идентификаторы с помощью цикла для количества затронутых строк, начиная с lastid (который является первым вставленным идентификатором массовой вставки). И, таким образом, я проверил, что он работает идеально ... только будьте осторожны, например, HeidiSQL не вернет правильное значение для ROW_COUNT (), вероятно, потому, что это дерьмовый графический интерфейс, делающий случайное дерьмо, о котором мы его не просим - однако это совершенно правильно из любого командная строка или PHP mysqli -

START TRANSACTION;
BEGIN;
INSERT into test (b) VALUES ('1'),('2'),('3');
SELECT LAST_INSERT_ID() AS lastid,ROW_COUNT() AS rowcount;
COMMIT;

В PHP это выглядит так (local_sqle - это прямой вызов mysqli_query, local_sqlec - это вызов mysqli_query + преобразование набора результатов в массив PHP):

local_sqle("START TRANSACTION;
BEGIN;
INSERT into test (b) VALUES ('1'),('2'),('3');");
$r=local_sqlec("SELECT LAST_INSERT_ID() AS lastid,ROW_COUNT() AS rowcount;");
local_sqle("
COMMIT;");
$i=0;
echo "last id =".($r[0]['lastid'])."<br>";
echo "Row count =".($r[0]['rowcount'])."<br>";

while($i<$r[0]['rowcount']){
    echo "inserted id =".($r[0]['lastid']+$i)."<br>";
    $i++;
}

Причина, по которой запросы разделены, заключается в том, что в противном случае я бы не получил свой результат, используя свои собственные функции, если вы сделаете это со стандартными функциями, вы можете вернуть его в один оператор, а затем получить нужный вам результат (это должен быть номер результата 2 - при условии, что вы используете расширение, которое обрабатывает более одного набора результатов / запросов).

person Morg.    schedule 23.09.2011

Я бы не был уверен, что значение автоинкремента увеличит элемент на 1. И возникнут огромные проблемы, если ваша БД будет иметь репликацию Master // Master и разрешить дублирование исключения auto_increment. AI будет +2 вместо +1, также, если будет еще один мастер, он станет +3. поэтому ретранслируйте такие вещи, как AUTO_INCREMENT, потому что 1 убивает ваш проект.

Я вижу только несколько хороших вариантов для этого.

этот фрагмент SQL не будет иметь проблем с несколькими мастерами и даст хорошие результаты, пока вам не понадобятся только вставленные записи. при нескольких запросах без транзакций можно поймать другие вставки записей.

START TRANSACTION;
SELECT max(id) into @maxLastId FROM `main_table`;
INSERT INTO `main_table` (`value`) VALUES ('first'), ('second') ON DUPLICATE KEY UPDATE `value` = VALUES(`value`);
SELECT `id` FROM `main_table` WHERE id > @maxLastId OR @maxLastId IS NULL;
COMMIT;

(если вам также понадобятся обновленные записи с помощью DUPLICATE KEY UPDATE), вам нужно будет немного реорганизовать базу данных, и SQL будет выглядеть следующим образом (безопасно для транзакций и никаких транзакций внутри одного соединения).

#START TRANSACTION    
INSERT INTO bulk_inserts VALUES (null);
SET @blukTransactionId = LAST_INSERT_ID();
SELECT  @blukTransactionId, LAST_INSERT_ID();
INSERT INTO `main_table` (`value`, `transaction_id`) VALUES ('first', @blukTransactionId), ('second', @blukTransactionId) ON DUPLICATE KEY UPDATE `value` = VALUES(`value`), `transaction_id` = VALUES(`transaction_id`);
SELECT  @blukTransactionId, LAST_INSERT_ID();
SELECT id FROM `main_table` WHERE `transaction_id` = @blukTransactionId;
#COMMIT

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

также эти параметры будут работать даже с INSERT IGNORE ...

person Neznajka    schedule 09.09.2019

Эта ветка старая, но все эти решения мне не помогли, поэтому я придумал свое.

Сначала посчитайте, сколько строк вы хотите вставить.

скажем, нам нужно добавить 5 строк:

LOCK TABLE tbl WRITE;

SELECT `AUTO_INCREMENT` FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'my_db' AND TABLE_NAME   = 'tbl'

затем используйте только что выбранный auto_increment для выполнения следующего запроса:

ALTER TABLE tbl AUTO_INCREMENT = {AUTO_INCREMENT}+5;
UNLOCK TABLES;

Наконец-то вставьте

Используйте зарезервированный диапазон автоинкремента для вставки с идентификатором.

Предупреждение: для этого решения требуется повышенный уровень доступа к таблицам. Но обычно массовые вставки выполняются скриптами crons и импортерами, а что-то еще может использовать специальный доступ в любом случае. Вы не стали бы использовать это только для нескольких вставок.

Это может оставить неиспользуемые идентификаторы, если вы используете ON DUPLICATE KEY UPDATE.

person Paul G Mihai    schedule 03.06.2020

Это можно решить другим способом, если вы знаете количество вставленных строк. Так, например, вы вставили 4 строки, последний идентификатор в вашей таблице был 600.

Итак, сразу после выполнения запроса на вставку вы можете использовать

select last_insert_id()

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

last_insert_id() значение, которое здесь будет 601 Итак, это будет 601 + 4 - 1 = 604

Итак, все идентификаторы между 601 и 604

Требуется InnoDB. Обязательно включите все запросы в транзакцию.

Изменить-
Решение 2 Работает только при наличии varchar или какого-либо текстового поля. Добавьте случайную строку в varchar или текст поле. Например, случайная строка - это 123sdfsklls113, у вас есть поле name, значение которого равно Sam, поэтому поле в этом случае станет

sam123sdfsklls113

Добавьте случайную строку в конец значения столбца.

После того, как вы вставили строки, теперь вы можете легко получить последние вставленные строки, используя этот

select id from table_name where column_name like '%randomstring_here'

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

update table_name set column_name = replace(column_name, 'randomstring_ here', '') where id in (last inserted ids here)

он удалит случайную строку из значения столбца.

person Koushik Das    schedule 26.05.2021

person    schedule
comment
это не запрос массовой вставки. - person Peacemoon; 10.09.2011