【问题标题】:Slow with bind parameters, even slower with JdbcTemplate使用绑定参数很慢,使用 JdbcTemplate 更慢
【发布时间】:2016-10-13 13:19:47
【问题描述】:

我的 Oracle 11g 数据库中有一个四列表,实现了扩展表反模式。我注意到一些查询花费了很长时间,并努力创建更好的索引;它在交互式会话中很好,但使用 Spring 的 NamedJdbcTemplate 仍然很慢。

考虑以下例程:

private void getObjectIds(ObjectDomain domain, HashMap<String, List<String>> dimensionMap)
    throws SQLException {
String sql = "SELECT m2.OBJECT_ID"
    + "  FROM MetaInformations m1, MetaInformations m2\n"
    + "  WHERE m1.OBJECT_ID = m2.OBJECT_ID\n"
    + "    AND m1.OBJECT_DOMAIN = :domain AND m1.KEY = :key1 AND\n"
    + "        m1.REF_OBJ_VALUE IN (:values1)\n"
    + "    AND m2.OBJECT_DOMAIN = :domain AND m2.KEY = :key2 AND\n"
    + "        m2.REF_OBJ_VALUE IN (:values2)";
String sqlWithBind = "SELECT m2.OBJECT_ID\n"
    + "  FROM MetaInformations m1, MetaInformations m2\n"
    + "  WHERE m1.OBJECT_ID = m2.OBJECT_ID\n"
    + "    AND m1.OBJECT_DOMAIN = ? AND m1.KEY = ? AND\n"
    + "        m1.REF_OBJ_VALUE IN (?, ?, ?, ?)\n"
    + "    AND m2.OBJECT_DOMAIN = ? AND m2.KEY = ? AND\n"
    + "        m2.REF_OBJ_VALUE IN (?)";

// Prebuilding statement, no bind variables left
Stopwatch stopWatch2 = Stopwatch.createStarted();
Iterator<Entry<String, List<String>>> entries = dimensionMap.entrySet().iterator();
Entry<String, List<String>> entry1 = entries.next();
Entry<String, List<String>> entry2 = entries.next();
String prebuilt = sql.replace(":domain", "'" + domain + "'")
    .replace(":key1", "'" + entry1.getKey() + "'")
    .replace(":values1",
        entry1.getValue().stream().map(s -> "'" + s + "'").collect(Collectors.joining(", ")))
    .replace(":key2", "'" + entry2.getKey() + "'")
    .replace(":values2",
        entry2.getValue().stream().map(s -> "'" + s + "'").collect(Collectors.joining(", ")));
Set<Long> rs2 = extractIdSet(getNamedParameterJdbcTemplate().queryForRowSet(prebuilt, Collections.emptyMap()));
log.warn("Prebuilt took: {} ms", stopWatch2.elapsed(TimeUnit.MILLISECONDS));

// Simple JDBCTemplate with 9 bind parameters
Stopwatch stopWatch5 = Stopwatch.createStarted();
Set<Long> rs1 = extractIdSet(getJdbcTemplate().queryForRowSet(sqlWithBind,
    domain.toString(),
    entry1.getKey(),
    entry1.getValue().get(0),
    entry1.getValue().get(1),
    entry1.getValue().get(2),
    entry1.getValue().get(3),
    domain.toString(),
    entry2.getKey(),
    entry2.getValue().get(0)));
log.warn("JdbcTemplate took: {} ms", stopWatch5.elapsed(TimeUnit.MILLISECONDS));

// Most beautiful: NamedJDBCTemplate
Stopwatch stopWatch3 = Stopwatch.createStarted();
Map<String, Object> paramMap = createNamedParameterMap(domain, dimensionMap);
Set<Long> rs3 = extractIdSet(getNamedParameterJdbcTemplate().queryForRowSet(sql, paramMap));
log.warn("NamedParameterJdbcTemplate took: {} ms", stopWatch3.elapsed(TimeUnit.MILLISECONDS));
}

这是结果。确切的时间因运行而异,但始终保持在同一数量级。

  1. 使用没有任何绑定参数的查询会很快完成,不到 100 毫秒
  2. 使用带有 9 个绑定变量的 Spring 的 JdbcTemplate,性能下降到爬行,大约 4 秒
  3. 最后,使用NamedJdbcTemplate,最简单,最灵活,和案例2一样慢;这至少不足为奇,因为幕后 NamedJdbcTemplate 将用命名参数替换我的查询,使其与案例 2 等效。

它没有获取连接,因为它们都是从同一个连接池中获取的。它似乎不仅仅是 queryForRowSet() 函数,因为这实际上也是最快的情况下使用的函数。同样,这似乎与 Spring 的异常翻译或参与正在进行的事务没有任何关系,因为这也会影响案例 1。

最后,问题是:与没有绑定参数的普通语句相比,为什么在这种情况下带有绑定参数的 Spring 的 JdbcTemplate 非常慢?

【问题讨论】:

  • 您的 gist 链接导致 404。将代码发布在问题本身中f
  • 代码是long。你真的想要它在问题中吗?无论如何,我修复了断开的链接,对此感到抱歉。
  • 是的,应该在问题中。这就是这里的规则。我们希望您的问题及其答案在 2 年内仍然可以理解,届时您的要点不再存在或已被修改。
  • 你在比较苹果和橘子。您正在使用查询执行和映射完整结果来测试普通查询执行。做一个公平的比较,做同样的事情,做同样的事情,不要将完成的一半工作与完成的所有工作进行比较。
  • 你是对的,不幸的疏忽。获取所有结果,或者只是更改代码以提取count(*),会导致所有带有绑定参数的变体同样慢。相应地编辑了问题;现在归结为“为什么绑定参数很慢”,这是我仍然不太了解的问题(无论如何,那是什么样的优化器?)但这并不新鲜。

标签: java spring oracle jdbc jdbctemplate


【解决方案1】:

原来它既不是JdbcTemplate也不是NamedJdbcTemplate。这也不是关于PreparedStatementStatement,即使后者是最快的。那只是因为普通语句不带有绑定参数。如果我有没有绑定参数的查询,它与原始 JDBC 和 NamedJdbcTemplate 的速度差不多。

我们的 Oracle 11g 只是为这个带有 9 个绑定参数的查询选择了一个糟糕的执行计划,并且不管实际参数是什么都坚持下去。我不知道为什么,也没有真正的 DBA 可用。

使用相同数据对 PostgreSQL 9.3 数据库进行的测试表明,无论有无绑定参数,它的速度都一样快;开箱即用的 Ubuntu 安装。

【讨论】:

  • 我知道这是一个老问题,但您找到解决方法了吗?我在使用 Oracle 12c 和 19c 时遇到了类似的问题。也许有一些方法可以给 Oracle 一个提示,使用哪个执行计划?
  • 从那以后我就没有仔细研究过。理论上,自适应光标共享应该可以缓解这种情况并绑定变量窥视,但它们都是在 12c 之前引入的。所以——小心你绑定的东西可能仍然是最好的建议。
  • 我遇到了类似的问题,但仅限于 String 参数。查询像Long id 这样的数字参数很快。我的 oracle DB 中的字符串字段定义为 VARCHAR。我的数据源配置有参数defaultNChar=true,因此每个输入字符串参数都被视为NVARCHAR 类型,Oracle DB 必须在后台为每个字符串值执行转换,因此索引不起作用。这是使用准备好的语句时的解决方法: ((OraclePreparedStatement)ps).setFormOfUse(columnIndex, FORM_CHAR); stackoverflow.com/q/26296220/2377807
猜你喜欢
  • 1970-01-01
  • 2018-05-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-02-19
  • 1970-01-01
  • 2021-04-11
  • 1970-01-01
相关资源
最近更新 更多