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