有没有办法改进这个请求?

Is there a way to improve this request?

环境:

我正在查询的 table/database 来自:

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 代码(注意 NODESMATCHERS 是由 jooq 的代码生成包生成的),但只有 nodes/NODES table 很有趣。

nodes中的一行table表示匹配事件;这里感兴趣的是 start_indexend_indexlevel 列。保证start_index小于等于end_indexlevel列是matcher树中的深度,深度从0开始;也就是说,对于匹配器路径 /a/b/c 中的某些匹配器 cclevel 将为 2.

现在,我要得到的结果如下:

给定一个行范围(10、25 或 50),return 一个映射,其中键是行号,值是该行的解析树的最大深度;只应被视为当前为此行激活的匹配器

一条线由间隔 [start, end)start 包含,end 不包含)具体化。如果以下两个语句都为真,则匹配器被认为对给定行有效:

现在,我如何解决这个问题:

代码:

@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));
}

如果我查询 25 行(即第 n 到 n + 24 行,对于某些 n),这个请求在 2200 万个 table 条目的最繁忙部分大约需要 15 秒,但是有没有改进方法?

请注意,更改 SQL 引擎不是一个选项;这是一个 GUI 应用程序,其数据库为 "forgettable";而且我不想要求安装 "full fledged" RDBMS 引擎!

我不太了解 H2,但是由于您的谓词总是会同时命中 START_INDEX END_INDEX,因此最好创建两列的索引:

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

原因是 SQL 引擎只需要访问磁盘并扫描索引一次,因为谓词的所有相关信息都已包含在索引中。这样会大大降低你的IO。

同样,不确定 H2 是否包含此内容(双关语意),但如果您还向索引添加 NODES.LEVEL,您将有一个所谓的 covering index,即索引包含此特定查询所需的所有数据,无需再次访问磁盘(对于 MAX() 函数):

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

Here's also a very interesting question about range queries on PostgreSQL.