Удаление миллионов строк в MySQL - PullRequest
63 голосов
/ 23 августа 2009

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

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

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

Ответы [ 11 ]

127 голосов
/ 23 августа 2009
DELETE FROM `table`
WHERE (whatever criteria)
ORDER BY `id`
LIMIT 1000

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

7 голосов
/ 23 августа 2009

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

6 голосов
/ 22 ноября 2017

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

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

CREATE TABLE 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 NOT IN (выберите отличный (temp_id) от id_temp_table);

  1. Переименование таблиц

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

6 голосов
/ 13 апреля 2016

следующее удаляет 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 = ",$1,";" }' | mysql; 
 done

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

3 голосов
/ 26 августа 2009

Я бы использовал mk-archiver из превосходного пакета утилит Maatkit (набор сценариев Perl для управления MySQL) Maatkit от Барона Шварца, автора O ' Рейли "High Performance MySQL" книга.

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

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

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

1 голос
/ 03 ноября 2018

Я столкнулся с подобной проблемой. У нас была действительно большая таблица, размером около 500 ГБ без разделов и один только один индекс для столбца primary_key. Наш мастер был огромным компьютером, 128 ядрами и 512 гигабайтами оперативной памяти, и у нас было несколько рабов. Мы попробовали несколько методов, чтобы заняться масштабным удалением строк. Я перечислю их всех здесь от худшего к лучшему, что мы нашли -

  1. Загрузка и удаление по одной строке за раз. Это самое худшее, что вы могли бы сделать. Итак, мы даже не пробовали это.
  2. Извлечение первых 'X' строк из базы данных с использованием запроса на ограничение в столбце primary_key, затем проверка идентификаторов строк для удаления в приложении и запуск одного запроса на удаление со списком идентификаторов primary_key. Итак, 2 запроса на «X» строк. Теперь, этот подход был хорош, но при использовании пакетного задания было удалено около 5 миллионов строк за 10 минут или около того, из-за чего ведомые устройства нашей базы данных MySQL отставали на 105 секунд. 105-секундное отставание в 10-минутной активности. Итак, нам пришлось остановиться.
  3. В этом методе мы вводили задержку в 50 мс между нашей последующей выборкой пакета и удалением размером 'X' каждая. Это решило проблему задержки, но теперь мы удаляли 1,2-1,3 миллиона строк за 10 минут по сравнению с 5 миллионами в методе № 2.
  4. Разделение таблицы базы данных, а затем удаление целых разделов, когда они не нужны. Это лучшее решение, которое у нас есть, но оно требует предварительно разделенной таблицы. Мы выполнили шаг 3, потому что у нас была очень старая многораздельная таблица с индексированием только по столбцу primary_key. Создание раздела заняло бы слишком много времени, и мы оказались в кризисном режиме. Вот несколько ссылок, связанных с разделением, которые я нашел полезными - Официальная справка по MySQL , Ежедневное разбиение Oracle DB .

Итак, ИМО, если вы можете позволить себе роскошь создать раздел в своей таблице, перейдите к варианту № 4, в противном случае вы застряли с параметром № 3.

1 голос
/ 29 ноября 2017

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

ВЫБРАТЬ из реплики для чтения список первичных ключей, которые вы хотите удалить. Экспорт в таком формате:

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

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

sql-chunker.sh (не забудьте chmod +x me и измените 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 "$1" ]
  then
    echo "No chunk size supplied. Invoke: ./sql-chunker.sh 1000 ids.txt"
fi

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

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

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

Вызывать так:

./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, это просто ярлык для входа без ввода пароля в командной строке.

1 голос
/ 06 марта 2016

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

TRUNCATE TABLE table_name

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

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

1 голос
/ 23 августа 2009

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

0 голосов
/ 29 апреля 2019

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

Это (очевидно) требует много дополнительного дискового пространства и может обложить ваши ресурсы ввода-вывода, но в противном случае может быть намного быстрее.

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

...