60

データベースに関しては、私は比較的初心者です。私たちは MySQL を使用しており、現在、実行に時間がかかると思われる SQL ステートメントを高速化しようとしています。SO で同様の質問を探しましたが、見つかりませんでした。

目標は、テーブル B で一致する ID を持つテーブル A のすべての行を削除することです。

現在、次のことを行っています。

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

テーブル a には約 100K 行、テーブル b には約 22K 行があります。列「id」は、両方のテーブルの PK です。

このステートメントは、私のテスト ボックス (Pentium D、XP SP3、2GB RAM、MySQL 5.0.67) で実行するのに約 3 分かかります。これは私には遅いようです。そうではないかもしれませんが、私は物事をスピードアップしたいと思っていました。これを達成するためのより良い/より速い方法はありますか?


編集:

役に立つかもしれないいくつかの追加情報。テーブル A と B の構造は、テーブル B を作成するために次の手順を実行した場合と同じです。

CREATE TABLE b LIKE a;

テーブル a (およびテーブル b) には、それに対して行われるクエリを高速化するのに役立ついくつかのインデックスがあります。繰り返しますが、私は DB 作業の比較的初心者であり、まだ学習中です。これが物事にどの程度の影響を与えるかはわかりません。インデックスもクリーンアップする必要があるため、効果があると思いますよね?また、速度に影響を与える可能性のある他のDB設定があるかどうかも疑問に思っていました.

また、私は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

解決した

すべての応答とコメントに感謝します。彼らは確かに私にその問題について考えさせました。「a.id を参照する他のテーブルはありますか?」という簡単な質問をすることで、問題を回避できるようにしてくれたdotjoeに感謝します。

問題は、テーブル A に、他の 2 つのテーブル C と D を更新するストアド プロシージャを呼び出す DELETE TRIGGER があったことです。 、それは声明を持っていました、

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回呼び出されることになるため(削除される特定のデータセットに対して)、それが問題であることがわかりました。その other_id 列に INDEX を作成し、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 ステートメントのほとんどは、本質的に同じ時間を要したことが判明しました。私はする必要がありました。この別のテーブル C にインデックスを指定すると、DELETE が高速になりました。

事後分析:
この演習から 2 つの教訓が得られました。まず、SQL クエリの影響をよりよく理解するために、EXPLAIN ステートメントの機能を活用していないことは明らかです。これは初歩的なミスなので、自分を責めるつもりはありません。私はその間違いから学びます。第二に、問題のあるコードは「すぐにやり遂げる」という考え方の結果であり、不適切な設計/テストにより、この問題がすぐに現れなかった. この新しい機能のテスト入力として使用するかなり大きなテスト データ セットをいくつか生成していれば、自分の時間もあなたの時間も無駄にはならなかったでしょう。DB 側での私のテストには、アプリケーション側の深さが欠けていました。今、私はそれを改善する機会を得ました。

参考:EXPLAIN文

4

14 に答える 14

9

あなたの 3 分間という時間は、本当に遅いようです。私の推測では、id 列のインデックスが適切に作成されていません。使用している正確なテーブル定義を提供できれば、それが役に立ちます。

テスト データを生成する単純な Python スクリプトを作成し、同じデータ セットに対して複数の異なるバージョンの削除クエリを実行しました。これが私のテーブル定義です:

drop table if exists a;
create table a
 (id bigint unsigned  not null primary key,
  data varchar(255) not null) engine=InnoDB;

drop table if exists b;
create table b like a;

次に、100k 行を a に、25k 行を b に挿入しました (うち 22.5k 行も a にありました)。さまざまな削除コマンドの結果を次に示します。ちなみに、実行の合間にテーブルを削除して再作成しました。

mysql> DELETE FROM a WHERE EXISTS (SELECT b.id FROM b WHERE a.id=b.id);
Query OK, 22500 rows affected (1.14 sec)

mysql> DELETE FROM a USING a LEFT JOIN b ON a.id=b.id WHERE b.id IS NOT NULL;
Query OK, 22500 rows affected (0.81 sec)

mysql> DELETE a FROM a INNER JOIN b on a.id=b.id;
Query OK, 22500 rows affected (0.97 sec)

mysql> DELETE QUICK a.* FROM a,b WHERE a.id=b.id;
Query OK, 22500 rows affected (0.81 sec)

すべてのテストは、Intel Core2 クアッドコア 2.5GHz、2GB RAM、Ubuntu 8.10 および MySQL 5.0 で実行されました。1 つの SQL ステートメントの実行は依然としてシングル スレッドであることに注意してください。


アップデート:

itsmatt のスキーマを使用するようにテストを更新しました。自動インクリメント(合成データを生成しています)と文字セットエンコーディング(機能していませんでした-掘り下げませんでした)を削除して、少し変更しました。

これが私の新しいテーブル定義です:

drop table if exists a;
drop table if exists b;
drop table if exists c;

create table c (id varchar(30) not null primary key) engine=InnoDB;

create table a (
  id bigint(20) unsigned not null primary key,
  c_id 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,
  key l_idx (l),
  key h_idx (h),
  key m_idx (m),
  key c_id_idx (id, c_id),
  key c_id_fk (c_id),
  constraint c_id_fk foreign key (c_id) references c(id)
) engine=InnoDB row_format=dynamic;

create table b like a;

次に、a に 100k 行、b に 25k 行を使用して同じテストを再実行しました (実行間で再入力します)。

mysql> DELETE FROM a WHERE EXISTS (SELECT b.id FROM b WHERE a.id=b.id);
Query OK, 22500 rows affected (11.90 sec)

mysql> DELETE FROM a USING a LEFT JOIN b ON a.id=b.id WHERE b.id IS NOT NULL;
Query OK, 22500 rows affected (11.48 sec)

mysql> DELETE a FROM a INNER JOIN b on a.id=b.id;
Query OK, 22500 rows affected (12.21 sec)

mysql> DELETE QUICK a.* FROM a,b WHERE a.id=b.id;
Query OK, 22500 rows affected (12.33 sec)

ご覧のとおり、おそらく複数のインデックスが原因で、これは以前よりもかなり遅くなります。ただし、3分台には及びません。

他に注目したいのは、ロングテキスト フィールドをスキーマの最後に移動することです。サイズが制限されたすべてのフィールドが最初にあり、テキスト、ブロブなどが最後にある場合、mySQL のパフォーマンスが向上することを覚えているようです。

于 2009-05-06T03:08:44.047 に答える
9

これは、超大規模なデータを操作する必要がある場合に常に行うことです (ここでは、150000 行のサンプル テスト テーブル)。

drop table if exists employees_bak;
create table employees_bak like employees;
insert into employees_bak 
    select * from employees
    where emp_no > 100000;

rename table employees to employees_todelete;
rename table employees_bak to employees;
drop table employees_todelete;

この場合、SQL は 50000 行をバックアップ テーブルにフィルター処理します。クエリ カスケードは、低速のマシンで 5 秒で実行されます。独自のフィルター クエリを使用して、insert を select に置き換えることができます。

これが、大きなデータベースで一括削除を実行するための秘訣です!;=)

于 2009-05-07T11:52:57.587 に答える
9

これを試して:

DELETE a
FROM a
INNER JOIN b
 on a.id = b.id

サブクエリの使用は、外部クエリの各レコードに対して実行されるため、結合よりも遅くなる傾向があります。

于 2009-05-01T18:15:55.533 に答える
3

これを試してください:

DELETE QUICK A.* FROM A,B WHERE A.ID=B.ID

通常のクエリよりもはるかに高速です。

構文については、http: //dev.mysql.com/doc/refman/5.0/en/delete.htmlを参照してください。

于 2009-05-01T19:27:58.993 に答える
3

「a」のすべての行に対して「b」でサブクエリを実行しています。

試す:

DELETE FROM a USING a LEFT JOIN b ON a.id = b.id WHERE b.id IS NOT NULL;
于 2009-05-01T18:17:39.380 に答える
3

OPのインデックス作成の省略により、この質問はほとんど解決されましたが、この追加のアドバイスを提供したいと思います。これは、この問題のより一般的なケースに有効です。

私は個人的に、あるテーブルから別のテーブルに存在する多くの行を削除する必要があることに対処しました。私の経験では、特に多くの行が削除されることが予想される場合は、次のことを行うのが最善です。この手法は最も重要なことに、レプリケーション スレーブ ラグを改善します。各ミューテーター クエリの実行時間が長くなるほど、ラグが悪化するためです (レプリケーションはシングル スレッドです)。

つまり、ここにあります:最初に SELECT を別のクエリとして実行し、スクリプト/アプリケーションで返された ID を記憶してから、バッチで削除を続けます (たとえば、一度に 50,000 行)。これにより、次のことが達成されます。

  • 各削除ステートメントはテーブルを長時間ロックしないため、レプリケーションの遅延が制御不能になることはありません。比較的最新のデータを提供するためにレプリケーションに依存している場合、これは特に重要です。バッチを使用する利点は、各 DELETE クエリに依然として時間がかかりすぎることがわかった場合、DB 構造に影響を与えずにクエリを小さく調整できることです。
  • 別の SELECT を使用するもう 1 つの利点は、特に何らかの理由で最適な DB インデックスを使用できない場合、SELECT 自体の実行に時間がかかる可能性があることです。SELECT が DELETE の内部にある場合、ステートメント全体がスレーブに移行するときに、もう一度 SELECT を実行する必要があり、長い選択を最初からやり直す必要があるため、スレーブに遅れが生じる可能性があります。スレーブ ラグは、再びひどく苦しんでいます。別の SELECT クエリを使用すると、渡すのは ID のリストだけなので、この問題はなくなります。

私のロジックのどこかに問題がある場合はお知らせください。

これと同様に、レプリケーション ラグとそれに対抗する方法の詳細については、「MySQL Slave Lag (Delay) Explained And 7 Ways To Battle It」を参照してください。

PS注意すべきことの1つは、もちろん、SELECTが終了してからDELETEが開始するまでの間にテーブルが編集される可能性があることです。アプリケーションに関連するトランザクションやロジックを使用して、そのような詳細を処理します。

于 2009-05-10T17:28:45.830 に答える
2

ところで、私のブログに上記を投稿した後、 PerconaのBaron Schwartzは、彼のmaatkitがすでにこの目的のためのツールであるmk-archiverを持っていることに気づきました。http://www.maatkit.org/doc/mk-archiver.html

それはおそらく仕事のためのあなたの最高のツールです。

于 2009-05-11T21:09:58.903 に答える
2

このような膨大なクエリを実行する前に、インデックスを再構築する必要があるかもしれません。まあ、定期的に再構築する必要があります。

REPAIR TABLE a QUICK;
REPAIR TABLE b QUICK;

次に、上記のクエリのいずれかを実行します(つまり)

DELETE FROM a WHERE id IN (SELECT id FROM b)
于 2009-05-06T10:09:44.737 に答える
2
DELETE FROM a WHERE id IN (SELECT id FROM b)
于 2009-05-01T18:20:20.047 に答える
2

クエリ自体はすでに最適な形式になっているため、インデックスを更新すると、操作全体にそれだけの時間がかかります。操作の前にそのテーブルのキーを無効にすることができます。これにより、処理が高速化されます。すぐに必要でない場合は、後でオンに戻すことができます。

別のアプローチは、deletedフラグ列をテーブルに追加し、他のクエリを調整してその値を考慮に入れることです。mysql で最速のブール型はCHAR(0) NULL(true = '', false = NULL) です。これは高速な操作であり、後で値を削除できます。

SQLステートメントで表現された同じ考え:

ALTER TABLE a ADD COLUMN deleted CHAR(0) NULL DEFAULT NULL;

-- The following query should be faster than the delete statement:
UPDATE a INNER JOIN b SET a.deleted = '';

-- This is the catch, you need to alter the rest
-- of your queries to take the new column into account:
SELECT * FROM a WHERE deleted IS NULL;

-- You can then issue the following queries in a cronjob
-- to clean up the tables:
DELETE FROM a WHERE deleted IS NOT NULL;

それもあなたが望むものではない場合は、削除ステートメントの速度についてmysqlドキュメントが何を言っているかを見ることができます。

于 2009-05-06T10:31:06.183 に答える
1

明らかに、操作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 

これらの設定がクエリの実行時間に影響するかどうかは、実際にはテストしていません。しかし、試してみる価値はあります。

于 2009-05-06T16:19:19.827 に答える
1

ターミナルを使用してデータベースに接続し、以下のコマンドを実行し、それぞれの結果時間を見てください。

  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;

1万件の削除にかかる時間は、10万件の削除の10倍ではありません。次に、レコードをより速く削除する方法を見つけることを除いて、いくつかの間接的な方法があります。

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
于 2014-03-03T08:37:21.277 に答える
-2

idフィールドを介して単一のテーブルでMySQLから複数のRowを削除するための基本的な手法

DELETE FROM tbl_name WHERE id <= 100 AND id >=200; このクエリは、特定のテーブルから 100 と 200 の間で一致した条件を削除する責任があります。

于 2016-12-12T04:28:07.253 に答える