Я хочу, чтобы выполнить следующий запрос:Выберите отчетливые быстрее, чем группа
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
запроса. Или как добавить индекс, чтобы он работал так же, как и есть? Или нужно какое-то другое исправление?
Я думаю, что на стороне 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
(Обсуждается в списке рассылки sqlite [здесь] (http://article.gmane.org/gmane.comp.db.sqlite.general/84279)) – Fabian
@Fabian Спасибо за внимание! Я добавил пример, используя восходящий 'ORDER BY', который показывает ту же проблему. Он также показывает эффект, который имеет индекс (он удаляет B-TREE в запросе SELECT DISTINCT и добавляет еще одно B-TREE в запросе «GROUP BY»). – Craigy