2

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

4

3 に答える 3

1

人口の多いテーブルを作成せずに確認することはできませんが、次のようなことを試してみます

-- This checks the SHA1 collisions
SELECT
  MIN(id) AS id,
FROM files
GROUP BY sha1
HAVING COUNT(*)>1
AND MIN(size)<>MAX(size)

-- This checks for directory duplicates
SELECT
  MIN(path) AS path
FROM directories
GROUP BY path
HAVING COUNT(*)>1

-- This checks for file duplicates
SELECT
  MIN(f.id) AS id
FROM files AS f
INNER JOIN files AS ff 
   ON f.dir_id=ff.dir_id
   AND f.filename=ff.filename
GROUP BY f.id
HAVING COUNT(*)>1

次々に実行します。

編集

3番目のクエリは大げさでした-申し訳ありません

于 2012-08-18T19:23:32.033 に答える
1

サブクエリの代わりに、UNIONと結合付きの2つの適切にインデックス付けされたクエリを使用してみてください。

まず、2つのインデックスが必要になります(指定したcreate_table.sqlのスキーマに基づく)。

ALTER TABLE files add key (sha1, size);
alter table files add key(filename, dir_id);

次に、クエリを書き直す必要があります。

(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 
INNER JOIN files files2
    USING(sha1)
WHERE files2.size != f.size)

UNION

(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
INNER JOIN files files2
    ON files2.id != f.id
   AND files2.filename = f.filename
INNER JOIN directories d2
   ON files2.dir_id = d2.id
  AND d2.path = d.path)
于 2012-08-18T19:23:59.723 に答える
0

2番目のサブクエリで一種のクロス結合を使用しますか?2番目のサブクエリを次のように変更してみてください。

SELECT ff2.id 
FROM files ff2 
WHERE ff2.id <> f.id 
AND ff2.dir_id  = d.dir_id 
AND ff2.filename = f.filename 

dir_id, filenameテーブル上にインデックスを作成しfilesます。

于 2012-08-18T19:19:43.900 に答える