MySQL INSERT ... ВЫБРАТЬ большой набор данных из 420 миллионов записей - PullRequest
0 голосов
/ 26 октября 2018

У меня большой набор данных приблизительно из 420 миллионов записей, и я смог загрузить их во временную таблицу своевременно, примерно за 15 минут, с помощью оператора LOAD DATA INFILE.Мне нужна эта временная таблица для размещения данных, потому что я выполняю некоторую очистку перед загрузкой в ​​конечный пункт назначения.

Временная таблица определяется как:

CREATE TABLE `temporary_data` (
  `t_id` smallint(10) unsigned NOT NULL,
  `s_name` varchar(512) NOT NULL,
  `record_type` varchar(512) NOT NULL,
  `record_value` varchar(512) NOT NULL
) ENGINE=MyISAM;

Целевая таблица, котораяэти загружаемые данные называются my_data и определяются следующим образом:

CREATE TABLE `my_data` (
  `s_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `t_id` smallint(10) unsigned NOT NULL,
  `s_name` varchar(63) NOT NULL,
  PRIMARY KEY (`s_id`),
  UNIQUE KEY `IDX_MY_DATA_S_NAME_T_ID` (`t_id`,`s_name`) USING BTREE,
  KEY `IDX_MY_DATA_S_NAME` (`s_name`) USING BTREE,
  CONSTRAINT `FK_MY_DATA_MY_PARENT` FOREIGN KEY (`t_id`) REFERENCES `my_parent` (`t_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

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

INSERT IGNORE INTO my_data (t_id, s_name)
SELECT t_id, s_name
FROM temporary_data;

Мне нужно определить способ ускорить этот запрос, чтобы он выполнялся своевременно (в идеале было бы менее 30 минут).

Некоторые подходы, которые я рассмотрел:

  1. Отключить индексы: Возможно, мне удастся отключить / удалить IDX_MY_DATA_S_NAME индекс, но я полагаюсь на уникальный индекс (IDX_MY_DATA_S_NAME_T_ID), чтобы сохранить данные в чистоте.Это ежедневный процесс, который будет запускаться автоматически, и неизбежно возникнут дубликаты.Кроме того, кажется, что потребуется перестроить индекс для набора данных такого большого размера, когда я снова включу индекс.
  2. Использовать DATA OUTFILE: Экспорти повторно импортируйте очищенные данные непосредственно в my_data.Я видел это рекомендованное где-то, но подумав об этом, индексы / PK все равно будут предметом спора при повторной вставке.
  3. Поменяйте местами таблицы: Замена my_data на temporary_dataЗвучит привлекательно, но в этой таблице много связей с внешними ключами для поля s_id, поэтому я хотел бы получить некоторую уверенность, что такой подход стоил бы хлопот с отключением внешних ключей и их повторным включением.Дочерние таблицы будут содержать значительно меньше записей, чем my_data, поэтому повторное включение внешних ключей может быть незначительным в этом отношении.
  4. Прямая загрузка INFILE данных: Загрузка данных непосредственно в my_dataиспользуя условные выражения в части SET оператора, чтобы сделать все поля NULL, если они не соответствуют критериям очистки, которые я первоначально применял к temporary_data перед загрузкой в ​​my_data.Это хакерство, но оно основано на предположении, что LOAD DATA INFILE будет быстрее, чем INSERT ... SELECT даже в условиях индексации, и будет только одна строка нулей, которую нужно удалить после запуска, из-за уникального ограничения таблицы.

Ничто из этого не звучит как ужасно великие идеи.Если у кого-то есть советы, я весь в ушах.

1 Ответ

0 голосов
/ 27 октября 2018

Избавьтесь от s_id, это, вероятно, бесполезно.Тогда рекламируйте UNIQUE(t_id, s_name) to be the PRIMARY KEY`.Это сокращает количество тестов, которые нужно выполнить для каждой вставленной строки.

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

INSERT IGNORE INTO my_data (t_id, s_name)
    SELECT t_id, s_name
    FROM temporary_data
    ORDER BY t_id, s_name;  -- Add this

Таким образом, вставки не прыгают в целевой таблице, таким образом (мы надеемся) избегая большого количества операций ввода-вывода.

Вы дополняете стол?Или заменить его?При замене есть гораздо лучший подход.

Подробнее ...

Заметили ли вы, что INSERT IGNORE тратит впустую значение AUTO_INCREMENT для каждой строки, которая не вставляется?Давайте попробуем другой подход ...

INSERT INTO my_data (t_id, s_name)
    SELECT t.t_id, t.s_name
        FROM temporary_data AS t
        LEFT JOIN my_data AS m  USING(t_id, s_name)
        WHERE m.s_id IS NULL
        ORDER BY t.t_id, t.s_name;

ORDER BY избегает прыгать во время INSERT.
LEFT JOIN ограничивает активность "новыми" строками.
НетAUTO_INCREMENT значения будут записаны.

Сколько строк будет вставлено каждый раз?Если это миллионы, то было бы лучше разбить его на куски.Смотрите мое обсуждение о чанкинге.Это может быть быстрее, чем построить огромный след отмены, чтобы в конечном итоге бросить.

Дальнейшее обсуждение - Учитывая

my_data:  PRIMARY KEY(s_id)  -- and s_id is AUTO_INCREMENT
my_data:  INDEX(t_id, s_name)
INSERT...SELECT...ORDER BY (t_id, s_name)  -- same as index

Это эффективно:

  • Поскольку ORDER BY и вторичный индекс одинаковы, добавления к индексу будут выполняться эффективно.
  • Между тем, новые значения AUTO_INCREMENT будут сгенерированы последовательно при«конец» таблицы.

Единственное, что было бы лучше, было бы, если бы (t_id, s_name) были уникальными.Тогда мы могли бы рассмотреть возможность полного избавления от s_id и изменения двух индексов на этот:

PRIMARY KEY(t_id, s_name)

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

PRIMARY KEY(t_id, s_name)
INDEX(s_id)   -- sufficient for AUTO_INCREMENT

. Я не знаю достаточно о большой картине и других запросах, чтобы судить, какое направление выбрать.Поэтому мое первоначальное предложение (до «Дальнейшего обсуждения») было «консервативным».

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