Nano Hash - криптовалюты, майнинг, программирование

Медленный запрос Informix COUNT/GROUP BY, даже с соответствующим индексом

У меня есть очень простой запрос, который выполняется медленно в Informix 11, хотя соответствующий индекс существует и используется:

select COUNTRY, COUNT(*) from EVENTS group by COUNTRY

Есть ли причина, по которой он должен работать медленно? У меня есть опыт выполнения подобных запросов с SQL Server, и они выполняются немедленно, если существует соответствующий индекс.

Больше информации:

  • Запрос занимает около 15 секунд для 500 000 записей в таблице EVENTS (что меня беспокоит, потому что в этой таблице будут миллионы записей, и я видел, что время выполнения быстро увеличивается).
  • Таблица EVENTS имеет индекс по СТРАНЕ. С помощью директивы EXPLAIN я проверил, что этот индекс используется.
  • В таблице EVENTS много столбцов (около 70).
  • Столбец "страна" - это varchar(32).
  • Для «страны» существует 25 различных значений.
  • Сканирование таблицы выполняется Informix:

1) informix.EVENTS: INDEX PATH

(1) Index Name: informix.country_ix Index Keys: COUNTRY (Serial, fragments: ALL) Query statistics: ----------------- Table map : ---------------------------- Internal name Table name ---------------------------- t1 EVENTS type table rows_prod est_rows rows_scan time est_cost ------------------------------------------------------------------- scan t1 501906 39285 501906 00:14.88 29390 type rows_prod est_rows rows_cons time est_cost ------------------------------------------------------------ group 25 4 501906 00:15.58 79761


  • Вы используете версию 11.10, 11.50 или 11.70? Вы запускали ОБНОВЛЕНИЕ СТАТИСТИКИ вообще? Не запускать это раньше было гораздо большей проблемой, чем в самых последних версиях (например, 12.10), но все же стоит проверить. На какой платформе вы работаете? Насколько велика строка в таблице событий? 08.03.2016
  • Он по-прежнему должен прочитать все страницы индекса, чтобы подсчитать количество событий для каждой страны. 09.03.2016
  • Моя версия Informix — 11.70.UC4D, работающая в Linux (Ubuntu). Я обновил статистику, но это не имеет значения. Однако (как я сказал Лоренцо ниже) при удалении индекса (и принудительном ПОСЛЕДОВАТЕЛЬНОМ СКАНИРОВАНИИ) запрос выполняется намного быстрее, что меня удивляет. Я попытаюсь рассчитать размер строки. В первом приближении это может быть около 1 КБ на строку. 10.03.2016

Ответы:


1

Итак, я сделал некоторые тесты.

TL;DR

  • Измените тип столбца страны на CHAR(32), перестройте индекс, и вы должны иметь гораздо лучшую производительность.

Длинная версия:

Используется informix 12.10FC6DE на linux centos 7 (ВМ создана в виртуальном боксе). Размер страницы, используемой для пространства базы данных, составлял 2048 байт, буферный пул — 50000 страниц.

Создал таблицу (tst) с размером строки около 425 байт (в среднем 4 строки на страницу) с несколькими столбцами. Из этих столбцов один — country VARCHAR(32), а другой — static_country CHAR(32). Заполнил таблицу 499999 строками со столбцами country и static_country, равномерно распределенными по 25 названиям стран.

Создал 2 индекса, один для столбца country (idx1_tst), а другой для столбца static_country (idx2_tst).

В разделе таблицы было использовано 125000 страниц данных (с использованием oncheck -pT). В индексах было использовано около 1500 страниц (с использованием oncheck -pT).

A. Запустите запрос несколько раз, запустив ПОСЛЕДОВАТЕЛЬНОЕ СКАНИРОВАНИЕ (время выполнения составляло от 10 до 15 секунд):

SELECT --+ FULL (tst)
    country, COUNT(*)
FROM
    tst
GROUP BY
    country

DIRECTIVES FOLLOWED:
FULL ( tst )
DIRECTIVES NOT FOLLOWED:

Estimated Cost: 1415645
Estimated # of Rows Returned: 25
Temporary Files Required For: Group By

  1) mydb.tst: SEQUENTIAL SCAN


Query statistics:
-----------------

  Table map :
  ----------------------------
  Internal name     Table name
  ----------------------------
  t1                tst

  type     table  rows_prod  est_rows  rows_scan  time       est_cost
  -------------------------------------------------------------------
  scan     t1     499999     499999    499999     00:12.17   140001

  type     rows_prod  est_rows  rows_cons  time       est_cost
  ------------------------------------------------------------
  group    25         25        499999     00:13.01   1275644

B. Запустите запрос несколько раз, запустив INDEX SCAN для индекса столбца country, который имеет тип VARCHAR(32) (время выполнения от 4 мин 30 с до 5 мин):

SELECT --+ INDEX (tst idx1_tst)
    country, COUNT(*)
FROM
    tst
GROUP BY
    country

DIRECTIVES FOLLOWED:
INDEX ( tst idx1_tst )
DIRECTIVES NOT FOLLOWED:

Estimated Cost: 3462411
Estimated # of Rows Returned: 25

  1) mydb.tst: INDEX PATH

    (1) Index Name: mydb.idx1_tst
        Index Keys: country   (Serial, fragments: ALL)


Query statistics:
-----------------

  Table map :
  ----------------------------
  Internal name     Table name
  ----------------------------
  t1                tst

  type     table  rows_prod  est_rows  rows_scan  time       est_cost
  -------------------------------------------------------------------
  scan     t1     499999     499999    499999     04:49.71   3462411

  type     rows_prod  est_rows  rows_cons  time       est_cost
  ------------------------------------------------------------
  group    25         25        499999     04:50.51   1275644

C. Запустите запрос несколько раз, запустив INDEX SCAN для индекса столбца static_country, который имеет тип CHAR(32) (время выполнения от 2 до 3 секунд):

SELECT --+ INDEX (tst idx2_tst)
    static_country, COUNT(*)
FROM
    tst
GROUP BY
    static_country

DIRECTIVES FOLLOWED:
INDEX ( tst idx2_tst )
DIRECTIVES NOT FOLLOWED:

Estimated Cost: 16428
Estimated # of Rows Returned: 25

  1) mydb.tst: INDEX PATH

    (1) Index Name: mydb.idx2_tst
        Index Keys: static_country   (Key-Only)  (Serial, fragments: ALL)


Query statistics:
-----------------

  Table map :
  ----------------------------
  Internal name     Table name
  ----------------------------
  t1                tst

  type     table  rows_prod  est_rows  rows_scan  time       est_cost
  -------------------------------------------------------------------
  scan     t1     499999     499999    499999     00:02.02   16429

  type     rows_prod  est_rows  rows_cons  time       est_cost
  ------------------------------------------------------------
  group    25         25        499999     00:02.72   1277132

Используя таблицу SMI sysptprof в базе данных sysmaster, я вижу следующие счетчики (используя onstat -z между запусками для сброса счетчиков):

  1. In case A (SEQUENCIAL SCAN):
    • table tst partition:
      • lockreqs 499999
      • isreads 125001
      • bufreads 500060
      • pagreads 117532
  2. In case B (INDEX SCAN on the VARCHAR type column):
    • table tst partition:
      • lockreqs 499999
      • isreads 499990
      • bufreads 999997
      • pagreads 348585
    • index idx1_tst partition:
      • lockreqs 499999
      • isreads 500009
      • bufreads 506961
      • pagreads 2545
  3. In case C (INDEX SCAN on the CHAR type column):
    • index idx2_tst partition:
      • lockreqs 499999
      • isreads 500000
      • bufreads 502879
      • pagreads 1440

Итак, для ПОСЛЕДОВАТЕЛЬНОГО СКАНИРОВАНИЯ, как я и ожидал, есть только активность в разделе таблицы.

Для INDEX SCAN в столбце CHAR есть только активность в разделе индекса, как я и ожидал (объяснение содержит указание Key-Only).

Для INDEX SCAN в столбце VARCHAR есть активность как в таблице, так и в индексных разделах, чего я не ожидал (но, как указал Фернандо, объяснение не содержит индикации Key-Only).

Я не могу объяснить такое поведение от informix. Но коллега указал мне на эту запись в руководстве по производительности informix (версия 12.10FC6, глава 10, план запроса, план доступа):

Важно: Оптимизатор не выбирает сканирование только по ключу для столбца VARCHAR. Если вы хотите воспользоваться преимуществами сканирования только по ключу, используйте ALTER TABLE с предложением MODIFY, чтобы изменить столбец на тип данных CHAR.

11.03.2016
  • Большое спасибо за тщательное расследование и подробные объяснения, Луис. Я перешел с VARCHAR на CHAR, и теперь запрос выполняется быстро (400 мс для 250 000 записей, 500 мс для 500 000 и 800 мс для 1 000 000). Теперь мой sqexplain говорит «Только ключ». Замена COUNT(*) на COUNT(1) ничего не меняет. 14.03.2016

  • 2

    Вещи, которые я бы попробовал:

    • COUNT(1) вместо COUNT(*) на случай, если СУБД тупая
    • тестирование запроса и проверка плана выполнения без индекса, потому что это может быть источником путаницы
    • проверка того, какие запросы ускоряет индекс, и проба различных типов индексов
    08.03.2016
  • Спасибо за подсказки, Лоренцо. Использование COUNT(country) или COUNT(1) не имеет значения. Однако при удалении индекса (при котором Informix переходит с INDEX PATH на SEQUENTIAL SCAN) запрос выполняется гораздо быстрее (5 секунд вместо 15). Интересно, почему! Я не могу придумать никакого другого индекса (отличного от того, который у меня есть: один столбец «страна»). 10.03.2016
  • Было бы интересно увидеть план запроса других вариантов. Сегодня я увидел этот эффект по аналогичному запросу. Примечательно, что в плане запроса не упоминается только ключ, и это идеально соответствует ответу, и было подтверждено, что доступ к разделу данных был осуществлен. Использование COUNT(1) должно избавиться от этого эффекта. 11.03.2016
  • Новые материалы

    Кластеризация: более глубокий взгляд
    Кластеризация — это метод обучения без учителя, в котором мы пытаемся найти группы в наборе данных на основе некоторых известных или неизвестных свойств, которые могут существовать. Независимо от..

    Как написать эффективное резюме
    Предложения по дизайну и макету, чтобы представить себя профессионально Вам не позвонили на собеседование после того, как вы несколько раз подали заявку на работу своей мечты? У вас может..

    Частный метод Python: улучшение инкапсуляции и безопасности
    Введение Python — универсальный и мощный язык программирования, известный своей простотой и удобством использования. Одной из ключевых особенностей, отличающих Python от других языков, является..

    Как я автоматизирую тестирование с помощью Jest
    Шутка для победы, когда дело касается автоматизации тестирования Одной очень важной частью разработки программного обеспечения является автоматизация тестирования, поскольку она создает..

    Работа с векторными символическими архитектурами, часть 4 (искусственный интеллект)
    Hyperseed: неконтролируемое обучение с векторными символическими архитектурами (arXiv) Автор: Евгений Осипов , Сачин Кахавала , Диланта Хапутантри , Тимал Кемпития , Дасвин Де Сильва ,..

    Понимание расстояния Вассерштейна: мощная метрика в машинном обучении
    В обширной области машинного обучения часто возникает необходимость сравнивать и измерять различия между распределениями вероятностей. Традиционные метрики расстояния, такие как евклидово..

    Обеспечение масштабируемости LLM: облачный анализ с помощью AWS Fargate и Copilot
    В динамичной области искусственного интеллекта все большее распространение получают модели больших языков (LLM). Они жизненно важны для различных приложений, таких как интеллектуальные..