Удаление миллионов строк в MySQL

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

  1. напишите сценарий, который будет выполнять тысячи меньших запросов удаления в цикле. Теоретически это обойдет проблему с заблокированной таблицей, потому что другие запросы смогут попасть в очередь и запускаться между удалениями. Но это все равно немного увеличит нагрузку на базу данных и займет много времени для запуска.
  2. переименуйте таблицу и воссоздайте существующую таблицу (теперь она будет пустой). Тогда делать моя очистка в переименованной таблице. Переименуйте новую таблицу, назовите старую и объедините новые строки в переименованную таблицу. Это путь принимает значительно больше шагов, но должен получить работу с минимальным перерывом. Единственная сложная часть здесь заключается в том, что таблица, о которой идет речь, - это таблица отчетов, поэтому, как только она переименована и пуста, все исторические отчеты уходят, пока я не верну их на место. Плюс процесс слияния может быть немного болезненным из-за тип хранимых данных. В целом, это мой вероятный выбор прямо сейчас.

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

8 ответов


DELETE FROM `table`
WHERE (whatever criteria)
ORDER BY `id`
LIMIT 1000

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


Я бы также рекомендовал добавить некоторые ограничения в вашу таблицу, чтобы убедиться, что это не произойдет с вами снова. Миллион строк, по 1000 за кадр, займет 1000 повторений сценария для завершения. Если сценарий выполняется один раз каждые 3,6 секунды, вы закончите через час. Не волнуйся. Ваши клиенты вряд ли заметят.


следующая удаляет 1 000 000 записей, по одной за раз.

 for i in `seq 1 1000`; do 
     mysql  -e "select id from table_name where (condition) order by id desc limit 1000 " | sed 's;/|;;g' | awk '{if(NR>1)print "delete from table_name where id = ",,";" }' | mysql; 
 done

вы можете сгруппировать их вместе и удалить table_name, где IN (id1, id2,..idN) я уверен, тоже без особых трудностей


У меня был случай использования удаления 1M + строк в таблице 25M + строк в MySQL. Пробовал разные подходы, такие как пакетные удаления (описанные выше).
Я узнал, что самый быстрый способ (копирование необходимых записей в новую таблицу):

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

создать таблицу id_temp_table (temp_id int);

  1. вставить идентификаторы, которые должны быть удалены:

вставить в id_temp_table (temp_id) выбирать.....

  1. создать новую таблицу table_new

  2. вставьте все записи из таблицы в table_new без лишних строк, которые находятся в id_temp_table

вставить в table_new .... где table_id не в (выберите distinct (temp_id) от id_temp_table);

  1. переименовать таблицы

весь процесс занял ~1час. в моем случае простое удаление пакета на 100 записей заняло 10 минут.


Я хотел бы использовать МК-архиватором от Maatkit пакет утилит (куча Perl-скриптов для управления MySQL) Maatkit от Барона Шварца, автора книги О'Рейли "высокопроизводительный MySQL".

цель-это низкий удар, только вперед работа откусывать старые данные из таблица без влияния запросов OLTP много. Вы можете вставить данные в другой таблица, которая не должна быть на одном и том же сервер. Вы также можете написать его файл в формате, подходящем для загрузки ИНФАЙЛ ДАННЫХ. Или вы можете делать ни в в каком случае это просто инкрементальное УДАЛИТЬ.

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

установка не требуется, просто захватить http://www.maatkit.org/get/mk-archiver и запустить perldoc на нем (или прочитать в интернете сайт) для документации.


По словам документация mysql, TRUNCATE TABLE является быстрой альтернативой DELETE FROM. Попробуйте это:

TRUNCATE TABLE table_name

я попробовал это на 50M строках, и это было сделано в течение двух минут.

Примечание: операции усечения не безопасны для транзакций; ошибка возникает при попытке одного в ходе активной транзакции или активной блокировки таблицы


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


для нас DELETE WHERE %s ORDER BY %s LIMIT %d ответ не был вариантом, потому что критерии WHERE были медленными (неиндексированный столбец) и попали бы в master.

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

00669163-4514-4B50-B6E9-50BA232CA5EB
00679DE5-7659-4CD4-A919-6426A2831F35

используйте следующий скрипт bash, чтобы захватить этот ввод и разбить его на операторы DELETE [требуется bash ≥ 4 из-за mapfile встроенный]:

sql-chunker.sh (вспомните chmod +x меня, и измените shebang, чтобы указать на ваш исполняемый файл bash 4):

#!/usr/local/Cellar/bash/4.4.12/bin/bash

# Expected input format:
: <<!
00669163-4514-4B50-B6E9-50BA232CA5EB
00669DE5-7659-4CD4-A919-6426A2831F35
!

if [ -z "" ]
  then
    echo "No chunk size supplied. Invoke: ./sql-chunker.sh 1000 ids.txt"
fi

if [ -z "" ]
  then
    echo "No file supplied. Invoke: ./sql-chunker.sh 1000 ids.txt"
fi

function join_by {
    local d=
    shift
    echo -n ""
    shift
    printf "%s" "${@/#/$d}"
}

while mapfile -t -n "" ary && ((${#ary[@]})); do
    printf "DELETE FROM my_cool_table WHERE id IN ('%s');\n" `join_by "','" "${ary[@]}"`
done < ""

Invoke следующим образом:

./sql-chunker.sh 1000 ids.txt > batch_1000.sql

это даст вам файл с выходной формат так (я использовал размер пакета 2):

DELETE FROM my_cool_table WHERE id IN ('006CC671-655A-432E-9164-D3C64191EDCE','006CD163-794A-4C3E-8206-D05D1A5EE01E');
DELETE FROM my_cool_table WHERE id IN ('006CD837-F1AD-4CCA-82A4-74356580CEBC','006CDA35-F132-4F2C-8054-0F1D6709388A');

затем выполните инструкции следующим образом:

mysql --login-path=master billing < batch_1000.sql

для тех, кто не знаком с login-path, это просто ярлык для входа в систему без ввода пароля в командной строке.