2013-10-07 2 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, результат почти мгновен. В качестве первого шага для отладки этого, я нашел фактический запрос SQL, что код DBIC становится:

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); 

Это работает практически мгновенно (не б-дерево).

РЕДАКТИРОВАТЬ: Чтобы показать, что проблема все еще возникает, когда 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

Я думаю, что на стороне sqlite не так много: GROUP BY обрабатывается в порядке возрастания, но ваш ORDER BY находится в порядке убывания. Даже когда вы создаете индексы как DESC (возможно в последней версии sqlite), вы все равно получаете b-дерево temp. Обратите внимание, что он исчезает, когда у вас есть ... 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', который показывает ту же проблему. Он также показывает эффект, который имеет индекс (он удаляет B-TREE в запросе SELECT DISTINCT и добавляет еще одно B-TREE в запросе «GROUP BY»). – Craigy

ответ

1

Примечание: Это не полный ответ на этот вопрос. Он показывает только, как избежать темпа b-дерева при сортировке по по возрастанию. При сортировке в по убыванию заказ необходим, в настоящее время AFAIK (версия 3.8.1) никоим образом (без настройки sqlite), чтобы избежать b-дерева temp для версии GROUP BY.

Используя определения таблиц и индексов от вопроса:

sqlite> select sqlite_version(); 
sqlite_version() 
---------------- 
3.8.1 

Ваш запрос выполняется без временных б деревьев, когда (а) вы приказываете в порядке возрастания и (б) предложения GROUP BY соответствует порядку 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 

Однако, когда вы приказываете в порядке убывания, вы получите временный б-дерево:

...> ... 
    ...> 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 без темп-дерева, вы должны настроить свое поколение SQL использовать DISTINCT.

Смежные вопросы