组相比明显快我想运行以下搜索:选择通过
schema->resultset('Entity')->search({
-or => { "me.user_id" => $user_id, 'set_to_user.user_id' => $user_id }
}, {
'distinct' => 1,
'join' => {'entity_to_set' => {'entity_set' => 'set_to_user'}},
'order_by' => {'-desc' => 'modified'},
'page' => 1,'rows' => 100
});
在有表的数据库,如下图所示。
CREATE TABLE entity (
id varchar(500) NOT NULL,
user_id varchar(100) NOT NULL,
modified timestamp NOT NULL,
PRIMARY KEY (id, user_id),
FOREIGN KEY (user_id) REFERENCES user(id) ON DELETE CASCADE ON UPDATE CASCADE
);
CREATE TABLE entity_to_set (
set_id varchar(100) NOT NULL,
user_id varchar(100) NOT NULL,
entity_id varchar(500) NOT NULL,
PRIMARY KEY (set_id, user_id, entity_id),
FOREIGN KEY (entity_id, user_id) REFERENCES entity(id, user_id) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (set_id) REFERENCES entity_set(id) ON DELETE CASCADE ON UPDATE CASCADE
);
CREATE TABLE entity_set (
id varchar(100) NOT NULL,
PRIMARY KEY (id)
);
CREATE TABLE set_to_user (
set_id varchar(100) NOT NULL,
user_id varchar(100) NOT NULL,
PRIMARY KEY (set_id, user_id),
FOREIGN KEY (user_id) REFERENCES user(id) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (set_id) REFERENCES entity_set(id) ON DELETE CASCADE ON UPDATE CASCADE
);
CREATE TABLE user (
id varchar(100) NOT NULL,
PRIMARY KEY (id)
);
我有大约6000 entity
,6000 entity_to_set
,10 entity_set
和50 set_to_user
。
现在,这个查询需要一些时间,(一两秒钟),这是不幸的。在仅对实体表进行查询时,包括ORDER BY
,结果几乎是即时的。至于调试这第一步,我发现DBIC代码变成实际的SQL查询:
SELECT me.id, me.user_id, me.modified FROM entity me
LEFT JOIN entity_to_set entity_to_set ON (entity_to_set.entity_id = me.id AND entity_to_set.user_id = me.user_id)
LEFT JOIN entity_set entity_set ON entity_set.id = entity_to_set.set_id
LEFT JOIN set_to_user set_to_user ON set_to_user.set_id = entity_set.id
WHERE ((set_to_user.user_id = 'Craigy' OR me.user_id = 'Craigy'))
GROUP BY me.id, me.user_id, me.modified ORDER BY modified DESC LIMIT 100;
这里是EXPLAIN QUERY PLAN
0|0|0|SCAN TABLE entity AS me USING INDEX sqlite_autoindex_entity_1 (~1000000 rows)
0|1|1|SEARCH TABLE entity_to_set AS entity_to_set USING COVERING INDEX entity_to_set_idx_cover (entity_id=? AND user_id=?) (~9 rows)
0|2|2|SEARCH TABLE entity_set AS entity_set USING COVERING INDEX sqlite_autoindex_entity_set_1 (id=?) (~1 rows)
0|3|3|SEARCH TABLE set_to_user AS set_to_user USING COVERING INDEX sqlite_autoindex_set_to_user_1 (set_id=?) (~5 rows)
0|0|0|USE TEMP B-TREE FOR ORDER BY
结果,其中entity_to_set_idx_cover
是
CREATE INDEX entity_to_set_idx_cover ON entity_to_set (entity_id, user_id, set_id);
现在,问题是用于排序的b-tree,而不是在我没有进行联接时使用的索引。
我注意到DBIx :: Class将'distinct' => 1
转换为GROUP BY
语句(I believe the documentation says they are equivalent here)。我删除了GROUP BY
声明和使用SELECT DISTINCT
代替,用下面的查询
SELECT DISTINCT me.id, me.user_id, me.modified FROM entity me
LEFT JOIN entity_to_set entity_to_set ON (entity_to_set.entity_id = me.id AND entity_to_set.user_id = me.user_id)
LEFT JOIN entity_set entity_set ON entity_set.id = entity_to_set.set_id
LEFT JOIN set_to_user set_to_user ON set_to_user.set_id = entity_set.id
WHERE ((set_to_user.user_id = 'Craigy' OR me.user_id = 'Craigy'))
ORDER BY modified DESC LIMIT 100;
我相信的结果相同。该EXPLAIN QUERY PLAN
此查询是
0|0|0|SCAN TABLE entity AS me USING COVERING INDEX entity_sort_modified_user_id (~1000000 rows)
0|1|1|SEARCH TABLE entity_to_set AS entity_to_set USING COVERING INDEX entity_to_set_idx_cover (entity_id=? AND user_id=?) (~9 rows)
0|2|2|SEARCH TABLE entity_set AS entity_set USING COVERING INDEX sqlite_autoindex_entity_set_1 (id=?) (~1 rows)
0|3|3|SEARCH TABLE set_to_user AS set_to_user USING COVERING INDEX sqlite_autoindex_set_to_user_1 (set_id=?) (~5 rows)
其中entity_sort_modified_user_id
是使用
CREATE INDEX entity_sort_modified_user_id ON entity (modified, user_id, id);
创建的索引这将运行几乎瞬间(无b树)。
编辑:为了演示当ORDER BY
按升序排列时问题仍然存在,以及索引对这些查询的影响,这里是对相同表格的类似查询。前两个查询没有索引,分别使用SELECT DISTINCT
和GROUP BY
,后两个查询和索引相同。
sqlite> EXPLAIN QUERY PLAN SELECT DISTINCT me.id, me.user_id, me.modified FROM entity me LEFT JOIN entity_to_set entity_to_set ON (entity_to_set.entity_id = me.id AND entity_to_set.user_id = me.user_id) LEFT JOIN entity_set entity_set ON entity_set.id = entity_to_set.set_id WHERE (me.user_id = 'Craigy' AND entity_set.id = 'SetID') ORDER BY modified LIMIT 100;
0|0|0|SCAN TABLE entity AS me (~100000 rows)
0|1|1|SEARCH TABLE entity_to_set AS entity_to_set USING AUTOMATIC COVERING INDEX (entity_id=? AND user_id=?) (~7 rows)
0|2|2|SEARCH TABLE entity_set AS entity_set USING COVERING INDEX sqlite_autoindex_entity_set_1 (id=?) (~1 rows)
0|0|0|USE TEMP B-TREE FOR DISTINCT
0|0|0|USE TEMP B-TREE FOR ORDER BY
sqlite> EXPLAIN QUERY PLAN SELECT me.id, me.user_id, me.modified FROM entity me LEFT JOIN entity_to_set entity_to_set ON (entity_to_set.entity_id = me.id AND entity_to_set.user_id = me.user_id) LEFT JOIN entity_set entity_set ON entity_set.id = entity_to_set.set_id WHERE (me.user_id = 'Craigy' AND entity_set.id = 'SetID') GROUP BY me.id, me.user_id, me.modified ORDER BY modified LIMIT 100;
0|0|0|SCAN TABLE entity AS me USING INDEX sqlite_autoindex_entity_1 (~100000 rows)
0|1|1|SEARCH TABLE entity_to_set AS entity_to_set USING AUTOMATIC COVERING INDEX (entity_id=? AND user_id=?) (~7 rows)
0|2|2|SEARCH TABLE entity_set AS entity_set USING COVERING INDEX sqlite_autoindex_entity_set_1 (id=?) (~1 rows)
0|0|0|USE TEMP B-TREE FOR ORDER BY
sqlite> CREATE INDEX entity_idx_user_id_modified_id ON entity (user_id, modified, id);
sqlite> EXPLAIN QUERY PLAN SELECT DISTINCT me.id, me.user_id, me.modified FROM entity me LEFT JOIN entity_to_set entity_to_set ON (entity_to_set.entity_id = me.id AND entity_to_set.user_id = me.user_id) LEFT JOIN entity_set entity_set ON entity_set.id = entity_to_set.set_id WHERE (me.user_id = 'Craigy' AND entity_set.id = 'SetID') ORDER BY modified LIMIT 100;
0|0|0|SEARCH TABLE entity AS me USING COVERING INDEX entity_idx_user_id_modified_id (user_id=?) (~10 rows)
0|1|1|SEARCH TABLE entity_to_set AS entity_to_set USING AUTOMATIC COVERING INDEX (entity_id=? AND user_id=?) (~7 rows)
0|2|2|SEARCH TABLE entity_set AS entity_set USING COVERING INDEX sqlite_autoindex_entity_set_1 (id=?) (~1 rows)
sqlite> EXPLAIN QUERY PLAN SELECT me.id, me.user_id, me.modified FROM entity me LEFT JOIN entity_to_set entity_to_set ON (entity_to_set.entity_id = me.id AND entity_to_set.user_id = me.user_id) LEFT JOIN entity_set entity_set ON entity_set.id = entity_to_set.set_id WHERE (me.user_id = 'Craigy' AND entity_set.id = 'SetID') GROUP BY me.id, me.user_id, me.modified ORDER BY modified LIMIT 100;
0|0|0|SEARCH TABLE entity AS me USING COVERING INDEX entity_idx_user_id_modified_id (user_id=?) (~10 rows)
0|1|1|SEARCH TABLE entity_to_set AS entity_to_set USING AUTOMATIC COVERING INDEX (entity_id=? AND user_id=?) (~7 rows)
0|2|2|SEARCH TABLE entity_set AS entity_set USING COVERING INDEX sqlite_autoindex_entity_set_1 (id=?) (~1 rows)
0|0|0|USE TEMP B-TREE FOR GROUP BY
0|0|0|USE TEMP B-TREE FOR ORDER BY
我的问题是:我怎么解决我DBIx ::类代码,以便它执行还有SELECT DISTINCT
查询。或者,如何添加索引以使其工作得很好?还是需要一些其他类型的修复?
我认为没有太多可以在SQLite端完成:GROUP BY按升序处理,但您的ORDER BY按降序排列。即使你创建索引为DESC(可能在最新版本的sqlite中),你仍然可以得到临时B树。请注意,当您有...... GROUP BY me.modified,me.user_id,me.id ORDER BY me.modified,me.user_id,me.id ASC LIMIT 100时会消失,但在使用DESC时不会。所以我想说,你必须解决SQL生成方面的主题。 – Fabian
(讨论在sqlite邮件列表[这里](http://article.gmane.org/gmane.comp.db.sqlite.general/84279)) – Fabian
@Fabian感谢您的关注!我添加了一个示例,使用升序ORDER BY来显示相同的问题。它还显示了索引具有的效果(它删除了“SELECT DISTINCT”查询中的B-TREE,并在“GROUP BY”查询中添加了另一个B-TREE)。 – Craigy