Удаление больших объемов данных из Mysql таблиц

Thank you for reading this post, don't forget to subscribe!

Если вам при­хо­дит­ся уда­лять десят­ки и сот­ни тысяч запи­сей из таб­лиц, вы зна­е­те, что эта рабо­та­ет мед­лен­но. Ясно, ведь Mysql в этом слу­чае дол­жен прой­тись по каж­дой запи­си и уда­лить её с диска.

В .io мы исполь­зу­ем (т.н.) окон­ные таб­ли­цы. Это когда вы хра­ни­те дан­ные в таб­ли­це все­го за послед­ний час (или дру­гой про­ме­жу­ток). А зна­чит, вы не толь­ко мно­го туда пиши­те, но и мно­го уда­ля­е­те. В слу­чае, если поте­ря дан­ных не фаталь­на, мож­но исполь­зо­вать MEMORY таб­ли­цы. Одна­ко для боль­шин­ства слу­ча­ев это, конеч­но, не подой­дет. Ведь терять дан­ные вся­кий раз при пере­за­груз­ке сер­ве­ра не хочется.

Почему DELETE работает медленно

Преж­де все­го, нуж­но пони­мать, что уда­ле­ние 10 тыс. запи­сей — это 10 тыс. уда­ле­ний по одной запи­си. А каж­дое уда­ле­ние — это несколь­ко опе­ра­ций запи­си изме­не­ний (в дан­ных и индек­сах) на диск.

Кро­ме это­го, преж­де чем уда­лить запи­си, Mysql дол­жен их сна­ча­ла выбрать. А в этом слу­чае исполь­зу­ют­ся те же пра­ви­ла, что и при выбор­ках. Если индек­сы настро­е­ны пло­хо, опе­ра­ция DELETE ста­нет еще медленнее.

Установка индексов

Для про­вер­ки исполь­зо­ва­ния индек­сов доста­точ­но DELETE заме­нить на SELECT count(*):

# Выяс­ним, исполь­зу­ет­ся ли здесь индекс

# Заме­на на SELECT поз­во­лит про­ве­рить исполь­зо­ва­ние индексов

Тогда смо­жем убе­дить­ся, что про­бле­ма в индексе:

# Сто­ит уста­но­вить индекс на колон­ку ts что­бы уско­рить удаление

Использование партиций

Индек­сы помо­гут при уда­ле­нии срав­ни­тель­но неболь­ших объ­е­мов. Одна­ко, если при­хо­дит­ся посто­ян­но уда­лять мно­го (как в нашем слу­чае), сто­ит посмот­реть на партиционирование.

Пар­ти­ци­о­ни­ро­ва­ние поз­во­ля­ет раз­бить таб­ли­цу на несколь­ко физи­че­ских блоков

Это зна­чит, что у нас будет воз­мож­ность мани­пу­ли­ро­вать отдель­ны­ми пар­ти­ци­я­ми. И вме­сто уда­ле­ния боль­шо­го коли­че­ства запи­сей, мы смо­жем обье­ди­нить их в один блок (пар­ти­цию) и уда­лить его одной операцией.

Что­бы про­ве­рить это на прак­ти­ке, созда­дим про­стую таб­ли­цу такой структуры:

# id, заго­ло­вок и дата созда­ния заголовка

Напол­ним ее тесто­вы­ми дан­ны­ми (несколь­ко десят­ков тысяч запи­сей) и уда­лим данные:

# Уда­лим часть дан­ных из таблицы

Запрос выпол­нил­ся доволь­но мед­лен­но, уда­лив око­ло 25 тыс. записей:

Убе­дим­ся, что про­бле­ма не в индек­се (мы его созда­ли, но на вся­кий слу­чай проверим):

Уви­дим, что индекс исполь­зу­ет­ся — тут все хорошо:

Про­ве­рим так­же систем­ную пере­мен­ную, кото­рая пока­зы­ва­ет коли­че­ство уда­лен­ных запи­сей из всех InnoDB таблиц:

# Уви­дим коли­че­ство уда­лен­ных строк

Мы про­во­дим экс­пе­ри­мент в изо­ли­ро­ван­ной сре­де, поэто­му дру­гих уда­ле­ний тут не происходит.

Выбор схемы партиционирования

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

# 24 пар­ти­ции пото­му, что 24 часа в сутках

Про­ве­рим, как выгля­дит рас­пре­де­ле­ние наших дан­ных по партициям:

# номер пар­ти­ции будет соот­вет­ство­вать часу колон­ки datetime

Как видим, дан­ные в таб­ли­це поме­ще­ны толь­ко в две пар­ти­ции. Они соот­вет­ству­ют теку­ще­му и преды­ду­ще­му часу. Что нам нуж­но — это очи­щать пар­ти­цию за тот час, кото­рый нам уже не нужен. Для это­го суще­ству­ет опе­ра­ция TRUNCATE :

# Эта опе­ра­ция выпол­ни­лась за (0.01 sec)

Если мы про­ве­рим счет­чик уда­лен­ных InnoDB запи­сей, уви­дим там:

# Зна­че­ние не изменилось

Это под­твер­жда­ет тот факт, что TRUNCATE рабо­та­ет прин­ци­пи­аль­но не так как DELETE. Вме­сто уда­ле­ния каж­дой запи­си, таб­ли­ца (или ее пар­ти­ция) очи­ща­ет­ся на уровне струк­ту­ры. Если очень гру­бо, то Mysql уда­ля­ет ста­рый файл дан­ных и созда­ет новый. А эта опе­ра­ция выпол­ня­ет­ся зна­чи­тель­но быст­рее построч­но­го удаления.

Если вам нуж­но уда­лять боль­шие объ­е­мы дан­ных из Mysql, сле­дуй­те двум советам:

  • Строй­те индек­сы для уско­ре­ния выбор­ки при уда­ле­нии, заме­нив DELETE FROM на EXPLAIN SELECT count(*) FROM.
  • Исполь­зуй­те пар­ти­ци­о­ни­ро­ва­ние и TRUNCATE PARTITION для эффек­тив­но­го уда­ле­ния боль­шо­го коли­че­ства строк.