SQLクエリのパフォーマンスの向上について多くの質問があったことは知っていますが、それらの質問の回答を使用してクエリのパフォーマンスを向上させることはできませんでした(十分)。
rsyncやfslintよりも柔軟なものが欲しかったので、ファイルツリーをウォークし、パスとチェックサムをmysqlデータベースに格納する小さなjavaツールを作成しました。
私のテーブル構造はここにあります: http ://code.google.com/p/directory-scanner/source/browse/trunk/sql/create_table.sql-最初はテーブルが1つしかありませんでしたが、その後、ディレクトリパスの冗長な非常に長い文字列を別の場所に移動して1:nの関係にすると、多くのスペースを節約できます。
これらの2つのインデックスを定義しました。
CREATE INDEX files_sha1 ON files (sha1);
CREATE INDEX files_size ON files (size);
今私を悩ませているクエリはそれらです: http ://code.google.com/p/directory-scanner/source/browse/trunk/sql/reporingQueries.sql
それらの最悪のものは最後のものであり、非常に高い確率で常に空のセットを返すはずです(sha1の衝突と誤って複数の挿入されたファイル):
SELECT
d.path,
d.id,
f.filename,
f.id,
f.size,
f.scandate,
f.sha1,
f.lastmodified
FROM files f
INNER JOIN directories d
ON d.id = f.dir_id
WHERE EXISTS ( /* same sha1 but different size */
SELECT ff.id
FROM files ff
WHERE ff.sha1 = f.sha1
AND ff.size <> f.size
)
OR EXISTS ( /* files with same name and path but different id */
SELECT ff2.id
FROM files ff2
INNER JOIN directories dd2
ON dd2.id = ff2.dir_id
WHERE ff2.id <> f.id
AND ff2.filename = f.filename
AND dd2.path = d.path
)
ORDER BY f.sha1
(インデックスを作成した後)20k行しかない限り、1秒以内に十分に実行されましたが、75万行になったので、文字通り何時間も実行され、mysqlはCPUコアの1つを完全に使い果たします。その間ずっと。
このクエリのEXPLAINは、次の結果をもたらします。
id ; select_type ; table ; type ; possible_keys ; key ; key_len ; ref ; rows ; filtered ; Extra
1 ; PRIMARY ; d ; ALL ; PRIMARY ; NULL ; NULL ; NULL ; 56855 ; 100.0 ; Using temporary; Using filesort
1 ; PRIMARY ; f ; ref ; dir_id ; dir_id ; 4 ; files.d.id ; 13 ; 100.0 ; Using where
3 ; DEPENDENT SUBQUERY ; dd2 ; ALL ; PRIMARY ; NULL ; NULL ; NULL ; 56855 ; 100.0 ; Using where
3 ; DEPENDENT SUBQUERY ; ff2 ; ref ; dir_id ; dir_id ; 4 ; files.dd2.id ; 13 ; 100.0 ; Using where
2 ; DEPENDENT SUBQUERY ; ff ; ref ; files_sha1 ; files_sha1 ; 23 ; files.f.sha1 ; 1 ; 100.0 ; Using where
私の他のクエリも75万行では迅速ではありませんが、少なくとも15分以内に終了します(ただし、数百万行でも機能するようにしたいと思います)。
更新:コメントをありがとうradashk、しかしあなたが提案したインデックスはmysqlによって自動的に作成されたようです->
"Table","Non_unique","Key_name","Seq_in_index","Column_name","Collation","Cardinality","Sub_part","Packed","Null","Index_type","Comment","Index_comment"
"files","0","PRIMARY","1","id","A","698397","NULL","NULL",,"BTREE",,
"files","1","dir_id","1","dir_id","A","53722","NULL","NULL",,"BTREE",,
"files","1","scanDir_id","1","scanDir_id","A","16","NULL","NULL","YES","BTREE",,
"files","1","files_sha1","1","sha1","A","698397","NULL","NULL","YES","BTREE",,
"files","1","files_size","1","size","A","174599","NULL","NULL",,"BTREE",,
UPDATE2:Eugen Rieckに感謝します!とにかく空のセットを返す可能性が最も高いので、あなたの答えはこのクエリの良い代替品だと思います。データを選択して、後で別のクエリで問題を説明するためにユーザーに表示します。私を本当に幸せにするために、誰かが私の他のクエリも見てくれるといいですね:D
UPDATE3:Justin Swanhartからの回答は、次の解決策に私を刺激しました。意図せずに複数回挿入されたディレクトリとファイルをチェックするクエリを実行する代わりに、次のような一意の制約を作成するだけです。
ALTER TABLE directories ADD CONSTRAINT uc_dir_path UNIQUE (path);
ALTER TABLE files ADD CONSTRAINT uc_files UNIQUE(dir_id, filename);
しかし、これが挿入ステートメントのパフォーマンスにどの程度悪影響を与えるのだろうか、誰かがこれについてコメントしてもらえますか?
UPDATE4:
ALTER TABLE directories ADD CONSTRAINT uc_dir_path UNIQUE (path);
長いので、動作しません。
ERROR 1071 (42000): Specified key was too long; max key length is 767 bytes
UPDATE5:
さて、これは私が最初の質問で上で引用したクエリを置き換えるために使用するソリューションです:
最初の部分では、sha1の衝突を見つけて、これを使用します。
SELECT sha1
FROM files
GROUP BY sha1
HAVING COUNT(*)>1
AND MIN(size)<>MAX(size)
そして、それが何かを返す場合は、別のクエリで詳細を選択しますWHERE sha1 =?
このインデックスを定義すると、このクエリが最適に実行されると思います。
CREATE INDEX sha1_size ON files (sha1, size);
重複するディレクトリが存在しないことを確認するために、これを使用します。彼は制約を許可していないためです(上記のUPDATE4を参照)。
SELECT path
FROM directories
GROUP BY path
HAVING COUNT(*)>1
そして、複製されたファイルについては、この制約を作成しようとします。
CREATE UNIQUE INDEX filename_dir ON files (filename, dir_id);
これは非常に高速(15〜20秒)で実行され、高速化する前に他のインデックスを作成する必要はありません。また、エラーメッセージには、問題をユーザーに表示するために必要な詳細が含まれています(挿入する前にそれらをチェックするため、とにかくありそうにありません)
現在、より短い時間で実行できるクエリはあと5つだけです;)これまでのところEugenとJustinの多大な支援に感謝します!
UPDATE6:わかりました。最後の返信から数日が経ちましたので、ジャスティンの回答を受け入れるつもりです。それが私を最も助けてくれたからです。私はあなたの両方から学んだことを私のアプリに組み込み、バージョン0.0.4をここでリリースしました:http ://code.google.com/p/directory-scanner/downloads/detail?name = directory-scanner-0.0.4- jar-with-dependencies.jar