MySql вставляет в запрос на выбор слишком медленно, чтобы скопировать 100 миллионов строк - PullRequest
0 голосов
/ 30 августа 2018

У меня есть одна таблица, содержащая более 100 миллионов строк, и я хочу скопировать данные в другую таблицу. У меня есть 1 требования, 1. Выполнение запроса не должно блокировать другие операции с этими таблицами базы данных, Я написал хранимую процедуру следующим образом

Я считаю количество строк в исходной таблице, затем получаю цикл, но копирую 10000 строк в каждой итерации, запускаю транзакцию и фиксирую ее. затем прочитайте следующие 10000 по смещению.

CREATE PROCEDURE insert_data()
BEGIN
  DECLARE i INT DEFAULT 0;
  DECLARE iterations INT DEFAULT 0;
  DECLARE rowOffset INT DEFAULT 0;
  DECLARE limitSize INT DEFAULT 10000;
  SET iterations = (SELECT COUNT(*) FROM Table1) / 10000;

  WHILE i <= iterations DO
    START TRANSACTION;
        INSERT IGNORE INTO Table2(id, field2, field3)
            SELECT f1, f2, f3
            FROM Table1
            ORDER BY id ASC
            LIMIT limitSize offset rowOffset;
    COMMIT;
    SET i = i + 1;
    SET rowOffset = rowOffset + limitSize;
  END WHILE;
END$$
DELIMITER ;

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

Ответы [ 3 ]

0 голосов
/ 30 августа 2018

Любой INSERT ... SELECT ... запрос получает блокировку SHARED для строк, которые он читает из исходной таблицы в SELECT. Но при обработке небольших кусков строк блокировка не длится слишком долго.

Запрос с LIMIT ... OFFSET будет выполняться все медленнее и медленнее по мере продвижения по исходной таблице. При 10000 строк на блок необходимо выполнить этот запрос 10000 раз, каждый из которых должен начинаться заново и сканировать таблицу, чтобы достичь нового СМЕЩЕНИЯ.

Независимо от того, что вы делаете, копирование 100 миллионов строк займет некоторое время. Это делает много работы.

Я бы использовал pt-archiver , бесплатный инструмент, разработанный для этой цели. Обрабатывает строки в «чанках» (или подмножествах). Он будет динамически регулировать размер чанков, чтобы каждый чанк занимал 0,5 секунды.

Самым большим отличием вашего метода от pt-archiver является то, что pt-archiver не использует LIMIT ... OFFSET, он идет по индексу первичного ключа, выбирая куски строки по значению, а не по положению. Таким образом, каждый кусок читается более эффективно.


Ваш комментарий:

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

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

Выполнение 20 000 дорогих запросов с использованием OFFSET займет больше времени, чем 10 000 похожих запросов. Самая дорогая часть не будет читать 5000 или 10000 строк или вставлять их в таблицу назначения. Дорогая часть будет пропускать ~ 50 000 000 строк снова и снова.

Вместо этого вам следует перебирать таблицу по значениям , а не по смещениям.

INSERT IGNORE INTO Table2(id, field2, field3)
        SELECT f1, f2, f3
        FROM Table1
        WHERE id BETWEEN rowOffset AND rowOffset+limitSize;

Перед циклом запросите значения MIN (id) и MAX (id), и начните rowOffset с минимального значения и выполните цикл до максимального значения.

Так работает pt-архиватор.

0 голосов
/ 01 сентября 2018

Спасибо @ Билл Карвин Я убрал смещение, как вы предложили. Следующий запрос работал на удивление хорошо,

DROP PROCEDURE IF EXISTS insert_identifierdataset;
DELIMITER $$
CREATE PROCEDURE insert_data()
BEGIN
  DECLARE i INT DEFAULT 0;
  DECLARE limitSize INT DEFAULT 2000;
  DECLARE maxId INT DEFAULT 0;

  SET maxId = (SELECT MAX(id) FROM Table1);

  WHILE i <= maxId DO
    START TRANSACTION;
        INSERT IGNORE INTO Table2(id, field1, field2)
            SELECT id, field3, field4
                FROM Table1
                WHERE id> i
                ORDER BY id ASC
                LIMIT limitSize;
    COMMIT;
    SET i = i + limitSize;
  END WHILE;
END$$  
0 голосов
/ 30 августа 2018

Блок - оперативное слово. Надеемся, что вы используете InnoDB (который блокирует на уровне записи), а не MyIsam (который блокирует на уровне таблицы). Не зная сложности данных или аппаратного обеспечения, 10K записей за цикл может быть слишком большим.

...