2013-10-07 116 views
4

組相比明顯快我想運行以下搜索:選擇通過

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 DISTINCTGROUP 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查詢。或者,如何添加索引以使其工作得很好?還是需要一些其他類型的修復?

+0

我認爲沒有太多可以在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

+0

(討論在sqlite郵件列表[這裏](http://article.gmane.org/gmane.comp.db.sqlite.general/84279)) – Fabian

+0

@Fabian感謝您的關注!我添加了一個示例,使用升序ORDER BY來顯示相同​​的問題。它還顯示了索引具有的效果(它刪除了「SELECT DISTINCT」查詢中的B-TREE,並在「GROUP BY」查詢中添加了另一個B-TREE)。 – Craigy

回答

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子句順序相匹配溫度B樹BY子句逐列。

查詢不變除了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-tree:

...> ... 
    ...> 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。