Почему подзапрос вызывает проверку, когда статический список этого не делает?

#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 ? Если нет, не удовлетворяет ли подзапрос (возможно, с помощью a distinct ) весь запрос?

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 .

Надеюсь, это поможет.