Более быстрый способ удаления совпадающих строк? - PullRequest
57 голосов
/ 01 мая 2009

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

Цель состоит в том, чтобы удалить все строки в таблице A, которые имеют совпадающий идентификатор в таблице B.

В настоящее время я делаю следующее:

DELETE FROM a WHERE EXISTS (SELECT b.id FROM b WHERE b.id = a.id);

В таблице a приблизительно 100 000 строк и около 22 000 строк в таблице b. Столбец id - это PK для обеих таблиц.

Это утверждение занимает около 3 минут для запуска на моем тестовом компьютере - Pentium D, XP SP3, 2 ГБ оперативной памяти, MySQL 5.0.67. Это кажется медленным для меня. Возможно, это не так, но я надеялся ускорить процесс. Есть ли лучший / более быстрый способ сделать это?


EDIT:

Некоторая дополнительная информация, которая может оказаться полезной. Таблицы A и B имеют ту же структуру, что и я, для создания таблицы B я сделал следующее:

CREATE TABLE b LIKE a;

В таблице a (и, следовательно, в таблице b) есть несколько индексов, помогающих ускорить выполнение запросов к ней. Опять же, я относительный новичок в работе с БД и все еще учусь. Я не знаю, какое влияние это оказывает на вещи. Я предполагаю, что это имеет эффект, так как индексы тоже должны быть очищены, верно? Мне также было интересно, есть ли другие параметры БД, которые могут повлиять на скорость.

Также я использую INNO DB.


Вот дополнительная информация, которая может быть вам полезна.

Таблица A имеет подобную структуру (я немного продезинфицировал ее):

DROP TABLE IF EXISTS `frobozz`.`a`;
CREATE TABLE  `frobozz`.`a` (
  `id` bigint(20) unsigned NOT NULL auto_increment,
  `fk_g` varchar(30) NOT NULL,
  `h` int(10) unsigned default NULL,
  `i` longtext,
  `j` bigint(20) NOT NULL,
  `k` bigint(20) default NULL,
  `l` varchar(45) NOT NULL,
  `m` int(10) unsigned default NULL,
  `n` varchar(20) default NULL,
  `o` bigint(20) NOT NULL,
  `p` tinyint(1) NOT NULL,
  PRIMARY KEY  USING BTREE (`id`),
  KEY `idx_l` (`l`),
  KEY `idx_h` USING BTREE (`h`),
  KEY `idx_m` USING BTREE (`m`),
  KEY `idx_fk_g` USING BTREE (`fk_g`),
  KEY `fk_g_frobozz` (`id`,`fk_g`),
  CONSTRAINT `fk_g_frobozz` FOREIGN KEY (`fk_g`) REFERENCES `frotz` (`g`)
) ENGINE=InnoDB AUTO_INCREMENT=179369 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;

Я подозреваю, что отчасти проблема в том, что для этой таблицы есть несколько индексов. Таблица B выглядит аналогично таблице B, хотя она содержит только столбцы id и h.

Кроме того, результаты профилирования следующие:

starting 0.000018
checking query cache for query 0.000044
checking permissions 0.000005
Opening tables 0.000009
init 0.000019
optimizing 0.000004
executing 0.000043
end 0.000005
end 0.000002
query end 0.000003
freeing items 0.000007
logging slow query 0.000002
cleaning up 0.000002

решаемые

Спасибо всем за отзывы и комментарии. Они, конечно, заставили меня задуматься о проблеме. Слава dotjoe за то, что я заставил меня отойти от проблемы, задав простой вопрос "Есть ли в других таблицах ссылка на a.id?"

?"

Проблема заключалась в том, что в таблице A был DELETE TRIGGER, который вызывал хранимую процедуру для обновления двух других таблиц, C и D. В таблице C был FK обратно в a.id, и после выполнения некоторых действий, связанных с этим идентификатором, в хранимая процедура, это было утверждение,

DELETE FROM c WHERE c.id = theId;

Я посмотрел на оператор EXPLAIN и переписал это как

EXPLAIN SELECT * FROM c WHERE c.other_id = 12345;

Итак, я мог видеть, что это делает, и это дало мне следующую информацию:

id            1
select_type   SIMPLE
table         c
type          ALL
possible_keys NULL
key           NULL
key_len       NULL
ref           NULL
rows          2633
Extra         using where

Это говорило мне, что это была болезненная операция, и, поскольку она собиралась вызываться 22500 раз (для данного набора данных, которые были удалены), это была проблема. После того, как я создал INDEX для этого столбца other_id и перезапустил EXPLAIN, я получил:

id            1
select_type   SIMPLE
table         c
type          ref
possible_keys Index_1
key           Index_1
key_len       8
ref           const
rows          1
Extra         

Гораздо лучше, на самом деле действительно здорово.

Я добавил, что Index_1 и мое время удаления соответствуют временам, указанным mattkemp . С моей стороны это было очень тонкой ошибкой из-за того, что в последнюю минуту были добавлены дополнительные функции. Оказалось, что большинство предложенных альтернативных операторов DELETE / SELECT, как заявил Даниэль , в конечном итоге заняли, по существу, такое же количество времени, и, как упоминалось soulmerge , это заявление было в значительной степени Лучше всего я смогу построить на основе того, что мне нужно было сделать. Как только я предоставил индекс для этой другой таблицы C, мои DELETE были быстрыми.

Посмертное
Из этого упражнения извлечены два урока. Во-первых, ясно, что я не использовал возможности оператора EXPLAIN, чтобы лучше понять влияние моих SQL-запросов. Это ошибка новичка, так что я не собираюсь изводить себя этим. Я учусь на этой ошибке. Во-вторых, нарушающий код был результатом менталитета «сделай это быстро», а неадекватный дизайн / тестирование привели к тому, что эта проблема не появилась раньше. Если бы я сгенерировал несколько значительных наборов тестовых данных для использования в качестве входных данных теста для этой новой функциональности, я бы не потратил впустую ни свое, ни ваше. Моему тестированию на стороне БД не хватало глубины, которую имеет моя прикладная сторона. Теперь у меня есть возможность улучшить это.

Ссылка: ОБЪЯСНИТЕ Заявление

Ответы [ 14 ]

2 голосов
/ 01 мая 2009
DELETE FROM a WHERE id IN (SELECT id FROM b)
1 голос
/ 03 марта 2014

Подключите базу данных с помощью терминала и выполните команду ниже, посмотрите на время результата каждой из них, вы обнаружите, что время удаления 10, 100, 1000, 10000, 100000 записей не умножается.

  DELETE FROM #{$table_name} WHERE id < 10;
  DELETE FROM #{$table_name} WHERE id < 100;
  DELETE FROM #{$table_name} WHERE id < 1000;
  DELETE FROM #{$table_name} WHERE id < 10000;
  DELETE FROM #{$table_name} WHERE id < 100000;

Время удаления 10 тысяч записей не в 10 раз больше, чем удаление 100 тысяч записей. Тогда, кроме как найти способ быстрее удалять записи, существуют некоторые косвенные методы.

1, мы можем переименовать table_name в table_name_bak, а затем выбрать записи из table_name_bak в table_name.

2, чтобы удалить 10000 записей, мы можем удалить 1000 записей 10 раз. Для этого есть пример сценария ruby.

#!/usr/bin/env ruby
require 'mysql2'


$client = Mysql2::Client.new(
  :as => :array,
  :host => '10.0.0.250',
  :username => 'mysql',
  :password => '123456',
  :database => 'test'
)


$ids = (1..1000000).to_a
$table_name = "test"

until $ids.empty?
  ids = $ids.shift(1000).join(", ")
  puts "delete =================="
  $client.query("
                DELETE FROM #{$table_name}
                WHERE id IN ( #{ids} )
                ")
end
1 голос
/ 06 мая 2009

Очевидно, что запрос SELECT, лежащий в основе вашей операции DELETE, довольно быстрый, поэтому я думаю, что ограничение внешнего ключа или индексы являются причинами вашего крайне медленного запроса.

Попробуйте

SET foreign_key_checks = 0;
/* ... your query ... */
SET foreign_key_checks = 1;

Это отключит проверки внешнего ключа. К сожалению, вы не можете отключить (по крайней мере, я не знаю как) обновления ключа с помощью таблицы InnoDB. С таблицей MyISAM вы можете сделать что-то вроде

ALTER TABLE a DISABLE KEYS
/* ... your query ... */
ALTER TABLE a ENABLE KEYS 

Я на самом деле не проверял, будут ли эти настройки влиять на продолжительность запроса. Но стоит попробовать.

0 голосов
/ 12 декабря 2016

Базовый метод для удаления нескольких строк MySQL в одной таблице через поле id

DELETE FROM tbl_name WHERE id <= 100 AND id >=200; Этот запрос отвечает за удаление соответствующего условия между 100 и 200 из определенной таблицы

...