Есть ли способ улучшить этот запрос?

1

Окружающая среда:

  • Java 8; в частности, Oracle JDK 1.8u25;
  • h2 как бэкэнд SQL;
  • jooq для запросов.

Таблица/база данных, к которой я обращаюсь:

private static final String H2_URI_PREFIX = "jdbc:h2:";
private static final String H2_URI_POSTFIX
    = ";LOG=0;LOCK_MODE=0;UNDO_LOG=0;CACHE_SIZE=131072";
private static final String H2_USERNAME = "sa";
private static final String H2_PASSWORD = "";

private static final List<String> H2_DDL = Arrays.asList(
   "create table matchers ("
        + " id integer not null,"
        + " class_name varchar(255) not null,"
        + " matcher_type varchar(30) not null,"
        + " name varchar(1024) not null"
        + ");",
    "create table nodes ("
        + " id integer not null,"
        + " parent_id integer not null,"
        + " level integer not null,"
        + " success integer not null,"
        + " matcher_id integer not null,"
        + " start_index integer not null,"
        + " end_index integer not null,"
        + " time long not null"
        + ");",
    "alter table matchers add primary key(id);",
    "alter table nodes add primary key(id);",
    "alter table nodes add foreign key (matcher_id)"
        + " references matchers(id)"
);

// ...

private void doDdl(final DSLContext jooq)
{
    H2_DDL.forEach(jooq::execute);

    jooq.createIndex("nodes_parent_id").on(NODES, NODES.PARENT_ID)
        .execute();
    jooq.createIndex("nodes_start_index").on(NODES, NODES.START_INDEX)
        .execute();
    jooq.createIndex("nodes_end_index").on(NODES, NODES.END_INDEX)
        .execute();
}

Даже если я показать полный код DDL здесь (обратите внимание, что NODES и MATCHERS генерируются jooq пакета генерации кода), только nodes/NODES таблица представляет интерес.

Одна строка в таблице nodes представляет событие соответствия; Здесь представлены start_index, end_index и level. Гарантируется, что start_index меньше или равно end_index; что касается столбца level, то это глубина в дереве сопряжения, а глубины начинаются с 0; то есть, в течение некоторого согласовани c в Сличитель пути /a/b/c, c level будет 2.

Теперь я хочу получить следующий результат:

Для диапазона строк (10, 25 или 50) верните карту, где ключи являются номером строки, а значения - максимальной глубиной дерева разбора для этой строки; следует рассматривать только как совпадающие, которые в настоящее время активны для этой линии

Линия материализуется интервалом [start, end) (start включительно, end исключение). Соединитель считается активным для данной строки, если выполняются оба следующих утверждения:

  • его начальный индекс строго меньше индекса конца строки; а также
  • его конечный индекс больше или равен индексу начала строки.

Теперь, как я решаю этот запрос:

  • Я создаю виртуальный столбец, состоящий из ряда операторов case SQL, по одному на строку, проверяя, активен ли соответствующий элемент для данной строки; этот столбец называется line;
  • Я select line, max(level) и группирую за line, с добавленным условием, что конечный индекс должен быть больше или равен первому индексу начала строки, а начальный индекс должен быть строго меньше, чем последний индекс конца строки.

Код:

@Override
public Map<Integer, Integer> getDepthMap(final int startLine,
    final int wantedLines)
    throws GrappaDebuggerException
{
    loadInputBuffer();

    final List<IndexRange> ranges
        = IntStream.range(startLine, startLine + wantedLines)
        .mapToObj(inputBuffer::getLineRange)
        .collect(Collectors.toList());

    final int startIndex = ranges.get(0).start;
    final int endIndex = ranges.get(ranges.size() - 1).end;
    final Condition indexCondition = NODES.START_INDEX.lt(endIndex)
        .and(NODES.END_INDEX.ge(startIndex));

    final Field<Integer> lineField = getLineField(startLine, ranges);

    final Map<Integer, Integer> ret = new HashMap<>();

    jooq.select(lineField, DSL.max(NODES.LEVEL))
        .from(NODES)
        .where(indexCondition)
        .groupBy(lineField)
        .forEach(r -> ret.put(r.value1(), r.value2() + 1));

    IntStream.range(startLine, startLine + wantedLines)
        .forEach(line -> ret.putIfAbsent(line, 0));

    return ret;
}

private Field<Integer> getLineField(final int startLine,
    final List<IndexRange> ranges)
{
    CaseConditionStep<Integer> step = DSL.decode()
        .when(activeThisRange(ranges.get(0)), startLine);

    final int size = ranges.size();

    for (int i = 1; i < size; i++)
        step = step.when(activeThisRange(ranges.get(i)), startLine + i);

    return step.as("line");
}

private static Condition activeThisRange(final IndexRange range)
{
    return NODES.START_INDEX.lt(range.end)
        .and(NODES.END_INDEX.ge(range.start));
}

Этот запрос занимает приблизительно 15 секунд в самых загруженных частях записи в 22 миллиона таблиц, если я запрашиваю 25 строк (т.е. Строки от n до n + 24 для некоторого n), но есть ли способ улучшить его?

Обратите внимание, что изменение механизма SQL НЕ является опцией; это приложение GUI, для которого базы данных "забыты"; и я не хочу требовать установки "полноценного" RDBMS-механизма!

Теги:
jooq
h2

1 ответ

1
Лучший ответ

Я не знаю H2 достаточно хорошо, но поскольку ваш предикат всегда будет START_INDEX как START_INDEX и END_INDEX, было бы лучше создать индекс для обоих столбцов:

 jooq.createIndex("better_index")
     .on(NODES, NODES.START_INDEX, NODES.END_INDEX)
     .execute();

Причина этого в том, что движок SQL должен будет ударить по диску и индексировать индексы только один раз, поскольку вся соответствующая информация для предиката уже содержится в индексе. Это значительно сократит ваш IO.

Опять же, не уверен, что H2 имеет это покрытие (каламбур), но если вы также добавите NODES.LEVEL в индекс, у вас будет так называемый индекс покрытия, то есть индекс, содержащий все данные, необходимые для этого конкретного запроса, удалив необходимость снова нажать диск (для функции MAX()):

 jooq.createIndex("covering_index")
     .on(NODES, 
         NODES.START_INDEX, 
         NODES.END_INDEX
         NODES.LEVEL)
     .execute();

Здесь также очень интересный вопрос о запросах диапазона на PostgreSQL.

  • 0
    Хм, почему диск ударил дважды из-за max() ? Кроме того, я не очень люблю "индексирование ковров" ...
  • 0
    Дважды: один раз для загрузки значений индекса и один раз для загрузки атрибута LEVEL из таблицы. Вот еще одна хорошая статья, объясняющая идею покрытия индексов . Опять же, я не знаю, работает ли все это в H2. Это, безусловно, будет работать в Oracle / PostgreSQL / SQL Server и т. Д. Что вы подразумеваете под «индексацией ковров»?
Показать ещё 7 комментариев

Ещё вопросы

Сообщество Overcoder
Наверх
Меню