Вопрос

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

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

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

Это было полезно?

Решение

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 = ",$1,";" }' | mysql; 
 done

Вы можете сгруппировать их вместе и удалить Table_name, где в (id1, id2, .. idn) Я тоже уверен, что без многочисленных сложности

У меня был использование случая удаления 1 м+ строк в таблице 25 м+ строк в 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 не в (выберите inclose (temp_id) из id_temp_table);

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

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

Я бы использовал MK-Archiver от отличного Маткит Утилит пакет (куча сценариев Perl для MySQL Management) Maatkit от барона Шварца, автора книги O'Reilly "MySQL".

Цель-низкопробранная, предпринимательская работа, чтобы выберека старых данных из таблицы, не влияя на запросы OLTP. Вы можете вставить данные в другую таблицу, которая не должна быть на одном сервере. Вы также можете написать его в файл в формате, подходящем для нагрузки данных. Или вы не можете сделать ни то, ни один из них, в этом случае это просто постепенное удаление.

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

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

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

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

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

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

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

TRUNCATE TABLE table_name

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

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

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

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

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

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

sql-chunker.sh (Запомни chmod +x я, и измените Шебанг, чтобы указать на ваш исполняемый файл 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

Это даст вам файл с форматированным выводом, как SO (я использовал размеры партии 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, это просто ярлык для входа, не вводя пароль в командной строке.

Я думаю, что медлительность связана с «кластеризованным индексом» MySQl, где фактические записи хранятся в индексе первичного ключа - в порядке индекса первичного ключа.Это означает, что доступ к записи через первичный ключ происходит чрезвычайно быстро, поскольку для этого требуется только одна выборка с диска, поскольку запись находится на диске именно там, где она нашла правильный первичный ключ в индексе.

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

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

Зная вышеизложенное, мы обнаружили, что реальное ускорение удаления в MySQL заключается в выполнении удалений в обратном порядке.Это приводит к наименьшему количеству перемещений записей, поскольку вы сначала удаляете записи с конца, а это означает, что при последующих удалениях будет меньше объектов для перемещения.

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top