Команда DELETE не заповнює таблицю рядків 30 000 000


22

Я успадкував базу даних і прагну очистити та пришвидшити її. У мене є таблиця, яка містить 30 000 000 рядків, багато з яких є непотрібними даними, введеними через помилку від імені нашого програміста. Перш ніж додати будь-які нові, більш оптимізовані індекси, я перетворив таблицю з MyISAM в InnoDB і шукаю видалити багато рядків, що містять непотрібні дані.

База даних - MySQL 5.0, і у мене є кореневий доступ до сервера. Я спочатку запускав ці команди через Adminer, а потім phpMyAdmin, обидві з однаковими результатами.

Команда, яку я виконую, -

DELETE
FROM `tablename`
WHERE `columnname` LIKE '-%'

По суті, видаліть що-небудь із цього стовпця, що починається з тире -.

Він працює близько 3-5 хвилин, а потім, коли я переглядаю список процесів, його немає.

Потім я біжу,

SELECT *
FROM `tablename`
WHERE `columnname` LIKE '-%'

і він повертає мільйони рядків.

Чому мій оператор видалення не завершується?

PS, я знаю, як застарілий MySQL 5.0. Я працюю над переміщенням БД до MySQL 5.6 w InnoDB (можливо, MariaDB 10 w XtraDB), але поки цього не відбудеться, я хочу відповісти на це за допомогою БД як є.

-

Редагування видалено, дивіться мою відповідь.

Відповіді:


24

Будь ласка, подивіться на архітектуру InnoDB (фото від КТ "Перкона" Вадима Ткаченка)

InnoDB сантехніка

Рядки, які ви видаляєте, записуються в журнали скасування. Файл ibdata1 зараз повинен зростати протягом тривалості видалення. За даними mysqlperformanceblog.comReasons for run-away main Innodb Tablespace :

  • Багато транзакційних змін
  • Дуже довгі транзакції
  • Нитка чистки

У вашому випадку причина №1 займе один відкатний сегмент разом із частиною місця для скасування, оскільки ви видаляєте рядки. Ці рядки повинні сидіти в ibdata1 до завершення видалення. Цей простір логічно викидається, але дисковий простір не скорочується.

Вам потрібно вбити це видалення прямо зараз. Після того, як ви заблокуєте запит на видалення, він відкине видалені рядки.

Ви робите це замість цього:

CREATE TABLE tablename_new LIKE tablename;
INSERT INTO tablename_new SELECT * FROM tablename WHERE `columnname` NOT LIKE '-%';
RENAME TABLE
    tablename TO tablename_old,
    tablename_new TO tablename
;
DROP TABLE tablename_old;

Ви могли це зробити спочатку проти версії таблиці MyISAM. Потім конвертуйте його в InnoDB.


21

Я думаю, що ми, можливо, надто ускладнили відповідь, яка була потрібна в моєму випадку . Я не сумніваюся, що обидві Роланд та Рік Джеймс вірно створюють тимчасову таблицю, вводячи лише рядки, які проходять фільтрNOT LIKE '-%' але рішення для мене було "простіше", тому що була важлива помилка, про яку я досі не знав і для що я вибачаюся.

Я запустив запит в mysqlінтерактивному рядку і помітив повідомлення про помилку,

mysql> DELETE FROM `slugs` WHERE `slug` LIKE '-%';
ERROR 1206 (HY000): The total number of locks exceeds the lock table size

Через помилку Google, я виявив, що рішення було збільшити innodb_buffer_pool_sizeчерез /etc/my.cnfфайл та перезавантажити демон mysql. Для мого сервера його було встановлено за замовчуванням, 8Mі я збільшив його 1G(сервер має 32 Гб, і це єдина таблиця, яка наразі є InnoDB).

mysql> DELETE FROM `slugs` WHERE `slug` LIKE '-%';
Query OK, 23517226 rows affected (27 min 33.23 sec)

Тоді я зміг запустити команду та видалити 23 мільйони записів за ~ 27 хвилин.

Для тих, хто цікавиться, що innodb_buffer_pool_sizeслід встановити, відзначте, скільки у вас є оперативної пам’яті, а потім подивіться на цей потік, який дає запропоновану оцінку в ГБ.


12

Пропозицію Роланда можна пришвидшити, виконавши обидві речі одночасно:

CREATE TABLE tablename_new LIKE tablename;
ALTER TABLE tablename_new ENGINE = InnoDB;
INSERT INTO tablename_new 
    SELECT * FROM tablename WHERE `columnname` NOT LIKE '-%' ORDER BY primary_key;
RENAME TABLE
    tablename TO tablename_old,
    tablename_new TO tablename
;
DROP TABLE tablename_old;

Але ось блог, в якому пояснюється, як робити великі ВИДАЛЕННЯ шматками, а не здаватися вічними: http://mysql.rjweb.org/doc.php/deletebig Суть полягає в тому, щоб пройти через стіл через ПК, роблячи 1K ряди одразу. (Звичайно, слід дізнатися більше деталей.)

І цей блог стосується потенційних досягнень при переході на InnoDB: http://mysql.rjweb.org/doc.php/myisam2innodb


5

Першим моїм інстинктом було б зробити кілька, менших видалень, обмеживши кількість результатів запиту та виконавши запит кілька разів:

DELETE
FROM `tablename`
WHERE `columnname` LIKE '-%' LIMIT 1000000

Недолік такого підходу: кожне видалення займе більше часу. Це відбувається тому, що для цього потрібно пропускати все більше і більше рядків, які не відповідають WHERE.
Рік Джеймс

Щоправда, але якщо цей процес не відбувається надто часто, багато сканування повних таблиць не повинно бути таким же поганим, як вирішена початкова проблема, а саме те, що запит ніколи не завершується через скасування розміру журналу.
kristianp

Дійсна точка. (Я зробив би LIMITнижню; скажімо, 10000.)
Рік Джеймс

4

Найпростіше рішення просто не робити цього - зробити меншу видалення, яку можна легше обробити.

У цьому випадку я рекомендував би спробувати послідовні видалення форми:

DELETE
FROM `tablename`
WHERE `columnname` LIKE '-a%'

2

Можливо, ви могли б зробити щось подібне:

  • Додайте нове поле під назвою deleted .
  • Зробіть оновлення, як UPDATE tablename SET deleted=1 WHERE `columnname` LIKE '-a%' .
  • Установіть, cronщоб видалити це вночі.

Оновлення може зайняти стільки, скільки видалення.
Рік Джеймс
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.