質問

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

目標は、テーブル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

解決済み

すべての回答とコメントに感謝します。彼らは確かに問題について考えさせられました。 dotjoe に感謝します。単純な質問<!> quot;他の表はa.idを参照しますか?<!> quot;

問題は、テーブルAにDELETE TRIGGERがあり、ストアドプロシージャを呼び出して他の2つのテーブル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回呼び出されるので、それが問題であることがわかりました。 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 によって報告された時間と一致していることを追加しました。これは、土壇場でいくつかの追加機能をシューホーンにしたため、私の側では本当に微妙なエラーでした。 Daniel が述べたように、提案された代替DELETE / SELECTステートメントのほとんどは、本質的に同じ時間を要し、 soulmerge が述べたように、ステートメントはきれいでした。私がやらなければならないことに基づいて構築することができようとしていた最高の。この他のテーブルCにインデックスを提供すると、DELETEは高速になりました。

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

リファレンス:EXPLAINステートメント

役に立ちましたか?

解決

InnoDBからのデータの削除は、要求できる最も費用のかかる操作です。既に発見したように、クエリ自体は問題ではありません-それらのほとんどはとにかく同じ実行計画に最適化されます。

すべてのケースのDELETEが最も遅い理由を理解するのは難しいかもしれませんが、かなり簡単な説明があります。 InnoDBはトランザクションストレージエンジンです。つまり、クエリが途中で中止された場合でも、何も起こらなかったように、すべてのレコードがそのまま残ります。完了すると、すべてが同じ瞬間に消えます。 DELETE中、サーバーに接続している他のクライアントには、DELETEが完了するまでレコードが表示されます。

これを実現するために、InnoDBはMVCC(Multi Version Concurrency Control)と呼ばれる手法を使用します。基本的には、各接続に、トランザクションの最初のステートメントが開始されたときのデータベース全体のスナップショットビューを提供します。これを実現するために、InnoDBのすべてのレコードは、内部的に複数の値を持つことができます(スナップショットごとに1つ)。これは、InnoDBでのCOUNTingに時間がかかる理由でもあります。それは、その時点で表示されるスナップショットの状態によって異なります。

DELETEトランザクションの場合、クエリ条件に従って識別される各レコードはすべて、削除のマークが付けられます。他のクライアントが同時にデータにアクセスしている可能性があるため、削除の原子性を保証するためにそれぞれのスナップショットを確認する必要があるため、それらをテーブルからすぐに削除することはできません。

すべてのレコードが削除対象としてマークされると、トランザクションは正常にコミットされます。 DELETEトランザクションの前にスナップショット値を処理した他のすべてのトランザクションも終了するまで、実際のデータページからそれらをすぐに削除することはできません。

したがって、トランザクションの安全な方法で削除する準備をするためにすべてのレコードを変更する必要があることを考えると、実際には3分はそれほど遅くありません。おそらくあなたは<!> quot;聞く<!> quot;ステートメントの実行中にハードディスクが動作します。これは、すべての行にアクセスすることにより発生します。 パフォーマンスを向上させるには、サーバーのInnoDBバッファープールサイズを増やし、削除中にデータベースへの他のアクセスを制限し、レコードごとにInnoDBが保持しなければならない履歴バージョンの数を減らします。 追加メモリにより、InnoDBは(ほとんど)テーブルをメモリに読み込み、ディスクシーク時間を回避できます。

他のヒント

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;

次に、10万行を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)

すべてのテストは、Ubuntu 8.10およびMySQL 5.0を搭載したIntel Core2クアッドコア2.5GHz、2GB RAMで実行されました。 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分マークの近くにはありません。

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

これを試してください:

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

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

これは、非常に大きなデータを操作する必要がある場合に常に行うことです(ここでは、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;

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

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

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

試してください:

DELETE FROM a USING a LEFT JOIN b ON a.id = b.id WHERE b.id IS NOT NULL;

これを試してください:

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

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

構文の参照: http://dev.mysql.com /doc/refman/5.0/en/delete.html

この質問は、OPのインデックス付けの欠落によりほぼ解決されていることは知っていますが、この追加のアドバイスを提供したいと思います。これは、この問題のより一般的な場合に有効です。

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

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

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

ロジックのどこかに障害があるかどうかを教えてください。

レプリケーションラグとこれに対抗する方法の詳細については、 MySQLスレーブラグ(遅延)の説明と7つの方法での戦い

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

DELETE FROM a WHERE id IN (SELECT id FROM b)

たぶん、このようなhughクエリを実行する前に、インデックスを再構築する必要があります。定期的に再構築する必要があります。

REPAIR TABLE a QUICK;
REPAIR TABLE b QUICK;

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

DELETE FROM a WHERE id IN (SELECT id FROM b)

クエリ自体はすでに最適な形式になっています。インデックスを更新すると、操作全体に時間がかかります。 そのテーブルのキーを無効にする前に操作、それは物事をスピードアップする必要があります。すぐにそれらを必要としない場合、後でそれらを再びつけることができます。

別のアプローチは、テーブルに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 docsが削除ステートメントの速度

ところで、私のブログに上記を投稿した後、Perconaの Baron Schwartz が彼の注意を喚起しました。 maatkit には既にこの目的のためのツールmk-archiverが既にあります。 http://www.maatkit.org/doc/mk-archiver.html

それはおそらく、仕事に最適なツールです。

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

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

ターミナルを使用してデータベースを接続し、以下のコマンドを実行し、それぞれの結果の時間を見ると、delete 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;

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

idフィールドを介して単一テーブル内の複数の行フォームMySQLを削除するための基本的なテクニック

DELETE FROM tbl_name WHERE id <= 100 AND id >=200; このクエリは、特定のテーブルから100〜200の一致条件を削除する役割を果たします

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top