我想运行以下搜索:
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树,而不是在我不进行连接时使用的索引。
我注意到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 :: Class代码,使其执行与SELECT DISTINCT
查询一样好。或者我如何添加索引以使其工作正常?还是需要其他一些修复?
答案 0 :(得分:1)
注意:这不是这个问题的完整答案。它仅显示在按升序顺序排序时如何避免临时b树。当需要按降序顺序排序时,AFAIK当前(版本3.8.1)没办法(没有调整sqlite)以避免GROUP BY版本的临时b树。
使用问题中的表定义和索引:
sqlite> select sqlite_version();
sqlite_version()
----------------
3.8.1
当(a)ORDER BY按升序排列并且(b)GROUP BY子句逐列匹配ORDER BY子句时,您的查询在没有临时b树的情况下运行。
除GROUP BY和ORDER BY子句外,查询保持不变:
/* table definitions as shown in the question */
sqlite> CREATE INDEX entity_to_set_idx_cover ON entity_to_set (entity_id, user_id, set_id);
sqlite> CREATE INDEX entity_sort_modified_user_id ON entity (modified, user_id, id);
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
...> 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.modified, me.user_id, me.id
...> ORDER BY me.modified, me.user_id, me.id ASC LIMIT 100;
selectid order from detail
---------- ---------- ---------- -------------------------------------------------------------------------
0 0 0 SCAN TABLE entity AS me USING COVERING INDEX entity_sort_modified_user_id
0 1 1 SEARCH TABLE entity_to_set AS entity_to_set USING COVERING INDEX entity_t
0 2 2 SEARCH TABLE entity_set AS entity_set USING COVERING INDEX sqlite_autoind
0 3 3 SEARCH TABLE set_to_user AS set_to_user USING COVERING INDEX sqlite_autoi
但是,当你按降序排序时,你得到一个临时b树:
...> ...
...> GROUP BY me.modified, me.user_id, me.id
...> ORDER BY me.modified, me.user_id, me.id DESC LIMIT 100;
selectid order from detail
---------- ---------- ---------- -------------------------------------------------------------------------
0 0 0 SCAN TABLE entity AS me USING COVERING INDEX entity_sort_modified_user_id
0 1 1 SEARCH TABLE entity_to_set AS entity_to_set USING COVERING INDEX entity_t
0 2 2 SEARCH TABLE entity_set AS entity_set USING COVERING INDEX sqlite_autoind
0 3 3 SEARCH TABLE set_to_user AS set_to_user USING COVERING INDEX sqlite_autoi
0 0 0 USE TEMP B-TREE FOR ORDER BY
原因是sqlite(直到当前版本的3.8.1)无法识别它可以按降序进行分组。因此,您将始终获得单独的步骤。即使将索引声明为DESC,也无法避免这种情况。请参阅有关sqlite mailing list的讨论。
<强>结论强> 如果您希望查询ORDER BY DESC而不使用临时b树,则必须调整SQL生成以使用DISTINCT。