具有子选择的MySQL查询性能 - 具有数百万行的表

时间:2012-08-18 19:05:30

标签: mysql sql indexing query-performance

我知道有很多关于sql查询性能改进的问题,但是我无法使用这些问题的答案来提高我的查询性能(足够)。

因为我想要一些比rsync更灵活的东西。 fslint,我写了一个小的java工具,它可以遍历文件树并存储路径和文件。 mysql数据库中的校验和。

你会在这里找到我的表结构: http://code.google.com/p/directory-scanner/source/browse/trunk/sql/create_table.sql - 起初我只有一个表,但后来我认为如果我将目录路径的冗余很长的字符串移动到一个单独的位置并使其成为1:n关系

,我可以节省大量空间

我已经定义了这两个索引:

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行(创建我的索引之后),它在不到一秒的时间内运行得很好,但是现在我有750k行,它的文学运行了几个小时,而mysql总计耗尽了我的一个cpu核心在整个时间。

此查询的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

我的其他查询也不是快速的750k行,但至少在15分钟内完成或类似的东西(但是,我希望它们也能用于数百万行..)

更新:感谢radashk的评论,但你建议的索引似乎是由mysql自动创建的 - &gt;

"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:

Okey,这是我将用于替换我在上一个问题中引用的查询的解决方案:

对于第一部分,找到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&amp;贾斯汀!

UPDATE6:好吧,所以既然距离任何人的最后一次回复还有几天,我就会接受Justin的回答,因为那是对我帮助最大的回答。我将从双方学到的知识合并到我的应用程序中,并在此发布了0.0.4版:http://code.google.com/p/directory-scanner/downloads/detail?name=directory-scanner-0.0.4-jar-with-dependencies.jar

3 个答案:

答案 0 :(得分: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

一个接一个地跑。

修改

第三次查询很尴尬 - 抱歉

答案 1 :(得分:1)

尝试使用UNION和两个带有连接的索引良好的查询,而不是子查询。

首先,您需要两个索引(基于您提供的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)

答案 2 :(得分:0)

你是否在第二个子查询中采用了一种交叉连接。尝试将第二个子查询更改为:

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上创建索引。