【问题标题】:Is there a way to improve this request?有没有办法改进这个请求?
【发布时间】:2015-04-09 22:02:55
【问题描述】:

环境:

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

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

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

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

一条线由间隔[start, end)start 包括,end 不包括)具体化。如果以下两个语句都为真,则认为匹配器对于给定行是活动的:

  • 它的开始索引严格小于行的结束索引;和
  • 其结束索引大于或等于行的开始索引。

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

  • 我创建了一个由一系列 SQL case 语句组成的虚拟列,每行一个,检查给定行的匹配器是否处于活动状态;此列名为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));
}

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

请注意,更改 SQL 引擎不是一种选择;这是一个 GUI 应用程序,其数据库是“容易忘记的”;而且我不想要求安装“成熟”的 RDBMS 引擎!

【问题讨论】:

    标签: java sql h2 jooq


    【解决方案1】:

    我不太了解 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.

    【讨论】:

    • 嗯,为什么磁盘会因为max()而被击中两次?另外,好吧,我有点不愿意“地毯索引”......
    • 两次:一次用于加载索引值,一次用于从表中加载LEVEL 属性。 Here's another good article explaining the idea of covering indexes。同样,我不知道这是否在 H2 中有效。它肯定会在 Oracle / PostgreSQL / SQL Server 等中工作。“地毯式索引”是什么意思?
    • “地毯索引”是我从this book 偷来的一个绰号,这意味着,基本上,“只是因为”在所有内容上添加索引......而且,索引越多,索引越差插入性能。虽然我只插入一次,但 2200 万行仍然需要一些时间;)
    • 无论如何,我会尝试使用覆盖索引查看;我也不希望这棵树经常被点击(毕竟你每秒只能点击有限的次数,而其他选项卡包含更多有趣的统计信息:p)。我真的等不及有一天我也可以将 jooq 用于所有 DDL:p
    • 嗯,至少start_indexend_index 上的索引实际上非常有用,我在不止一个查询中都过滤了!公认。感谢jooq!
    猜你喜欢
    • 1970-01-01
    • 2020-07-10
    • 1970-01-01
    • 2021-10-31
    • 1970-01-01
    • 1970-01-01
    • 2019-11-28
    • 2020-03-09
    • 1970-01-01
    相关资源
    最近更新 更多