#sql #sqlite
#sql #sqlite
Вопрос:
У меня есть запрос, содержащий подзапрос, который всегда вызывает СКАНИРОВАНИЕ очень большой таблицы, что приводит к сокращению времени запроса.
Это запрос, который я использую:
SELECT PersonId
FROM person
WHERE PersonId IN (
SELECT PersonId
FROM relationship
WHERE RelatedToPersonId = 12270351721
);
План запроса сообщается как:
SCAN TABLE person (~100000 rows)
EXECUTE LIST SUBQUERY 1
SEARCH TABLE relationship USING INDEX relationship_RelatedToPersonId_IDX (RelatedToPersonId=?) (~10 rows)
Тот же запрос со статическим списком (эквивалентный результатам подзапроса):
SELECT PersonId
FROM person
WHERE PersonId IN (12270351727,12270351730,12270367969,12387741400);
И план запроса для этого:
SEARCH TABLE person USING COVERING INDEX sqlite_autoindex_person_1 (PersonId=?) (~5 rows)
EXECUTE LIST SUBQUERY 1
Почему первый запрос запрашивает проверку, если второй этого не делает?
Комментарии:
1. Если набор данных из вашего подзапроса невелик, может работать временная таблица. Что-то вроде: Выберите Personid Из (ВЫБЕРИТЕ PersonID ИЗ отношения, ГДЕ RelatedToPersonId=12270351721);
2. Я попробовал временную таблицу, и она все равно произвела сканирование.
3. Может ли a
PersonId
существовать вrelationship
, если в нем нет строкиperson
? Если нет, не удовлетворяет ли подзапрос (возможно, с помощью adistinct
) весь запрос?4. Это надуманный запрос для демонстрации проблемы. Реальный запрос выполняет более сложный подзапрос и возвращает другие столбцы из person, но отображает то же неэффективное поведение.
Ответ №1:
Вероятно, несоответствующие типы данных. Делает человек.PersonID и relationship.PersonID имеют одинаковое определение данных.
Ответ №2:
Таблица должна быть проверена, потому что вы включаете другое поле (RelatedToPersonId ) в предложение WHERE вашего подзапроса. Список идентификаторов персон может перейти непосредственно к индексу.
Комментарии:
1. RelatedToPersonId находится в таблице отношений. Почему это может вызвать сканирование таблицы person? Разве этот подзапрос не должен выполняться независимо, создавать список результатов, а затем вести себя так же, как запрос со статическим списком значений? Если вы посмотрите на план запроса для этого запроса, часть подзапроса фактически попадает в индекс.
2. Оптимизатор SQLite определенно должен знать, что подзапрос может использовать индекс, и обязательно сначала выполнить его, а затем ограничить строки из таблицы person только этим результирующим набором.
Ответ №3:
Я не могу воспроизвести. Я выполнил следующие инструкции при новой установке SQLite версии 3.7.8:
create table person (id int not null primary key, name varchar(20));
insert into person values (1, 'a');
insert into person select id 1, name || 'b' from person;
insert into person select id 2, name || 'c' from person;
insert into person select id 4, name || 'd' from person;
insert into person select id 8, name || 'e' from person;
insert into person select id 16, name || 'f' from person;
insert into person select id 32, name || 'g' from person;
insert into person select id 64, name || 'h' from person;
insert into person select id 128, name || 'i' from person;
insert into person select id 256, name || 'j' from person;
insert into person select id 512, name || 'k' from person;
insert into person select id 512, name || 'l' from person;
insert into person select id 1024, name || 'l' from person;
insert into person select id 2048, name || 'm' from person;
insert into person select id 4096, name || 'n' from person;
insert into person select id 8192, name || 'o' from person;
insert into person select id 16384, name || 'p' from person;
insert into person select id 32768, name || 'q' from person;
insert into person select id 65536, name || 'r' from person;
select count(*) from person;
create table relation (id int, related int);
insert into relation select id, id 1 from person;
insert into relation select id, id 2 from person;
insert into relation select id, id 3 from person;
insert into relation select id, id 4 from person;
insert into relation select id, id 5 from person;
insert into relation select id, id 6 from person;
insert into relation select id, id 7 from person;
insert into relation select id, id 8 from person;
insert into relation select id, id 9 from person;
insert into relation select id, id 10 from person;
delete from relation where related not in (select id from person);
create index relatedToPerson on relation(related);
explain query plan select id from person
where id in (select id from relation where related = 2345);
Результаты для инструкции плана запроса:
0|0|0|SEARCH TABLE person USING COVERING INDEX sqlite_autoindex_person_1 (id=?)(~25 rows)
0|0|0|EXECUTE LIST SUBQUERY 1
1|0|0|SEARCH TABLE relation USING INDEX relatedToPerson (related=?) (~10 rows)
Почему у вас это не работает? Причины, о которых я могу думать:
- ваша связь с таблицей не содержит столбца PersonID (пожалуйста, проверьте)
- вы используете другую версию SQLite
- у вас есть другие ограничения, например, уникальные индексы.
Можете ли вы запустить приведенный выше сценарий и проверить, получаете ли вы те же результаты, что и я?
Комментарии:
1. Я был слишком занят на этой неделе, чтобы заняться этим, и завтра я уезжаю из города, поэтому, возможно, я не смогу уделить этому больше времени еще несколько дней. Но я не хочу тратить вознаграждение впустую, поэтому я присуждаю его вам за приложенные вами усилия. Если ваш пример действительно не вызывает сканирования, то это даст мне хорошую возможность начать, пытаясь понять, почему мой не работает так же.
2. Удачи. Пожалуйста, опубликуйте свои результаты здесь. Мне любопытно, в чем разница.
3. Несовпадающие типы данных (текст или целое число)
Ответ №4:
Я не уверен, почему существует сканирование таблицы. Но здесь нет необходимости использовать подвыбор: вы также можете использовать соединение:
SELECT person.PersonId
FROM person JOIN relationship ON person.PersonId = relationship.PersonId
WHERE RelatedToPersonId = 12270351721;
Не уверен, поддерживает ли sqlite этот синтаксис соединения, но его также можно переписать с помощью FROM и WHERE:
SELECT person.PersonId
FROM person, relationship
WHERE person.PersonId = relationship.PersonId
AND RelatedToPersonId = 12270351721;
И, возможно, это больше то, на что настроен оптимизатор.
Комментарии:
1. Как я упоминал в комментариях выше, это надуманный запрос, который демонстрирует очень простой случай неожиданного сканирования. Запрос «реальной жизни» существенно сложнее, но сводится к той же ситуации — подзапрос, который создает короткий список, который производит проверку, помещая те же значения в статический список, не вызывает проверку.
Ответ №5:
Это:
SELECT PersonId
FROM person
WHERE PersonId IN (12270351727,12270351730,12270367969,12387741400);
это просто синтаксический сахар для этого:
SELECT PersonId
FROM person
WHERE (
PersonId = 12270351727
OR PersonId = 12270351730
OR PersonId = 12270367969
OR PersonId = 12387741400
);
Комментарии:
1. Да, но это не объясняет, почему сканирование необходимо при использовании подзапроса или, что более важно, как избежать этого сканирования. Просто кажется, что оптимизатор запросов SQLite в этом случае не создает оптимальный план запроса.
Ответ №6:
Это вызывает полное сканирование, потому что подзапрос выполняется для каждой строки в person
. Подзапросы не «кэшируют» свой результирующий набор для преобразования IN
.
Надеюсь, это поможет.