Rob*_*roj 15 java performance hibernate jdbc hibernate-6.x
Hibernate团队宣布Hibernate 6时宣称,通过在JDBC ResultSet中从按名称读取切换为按位置读取,它们可以获得性能上的好处。
高负载性能测试表明,Hibernate按名称从ResultSet中读取值的方法是其扩展吞吐量的最大限制因素。
这是否意味着他们将呼叫从更改 getString(String columnLabel)为getString(int columnIndex)?
为什么这样更快?
由于ResultSet是一个接口没有性能增益是否取决于执行它的JDBC驱动程序?
收益有多大?
Mar*_*eel 12
作为JDBC驱动程序维护者(我承认,做了一些概括性的归纳并不一定适用于所有JDBC驱动程序),行值通常将存储在数组或列表中,因为最自然地与从数组中接收数据的方式相匹配。数据库服务器。
结果,按索引检索值将是最简单的。它可能像这样简单(忽略实现JDBC驱动程序的一些更原始的细节):
public Object getObject(int index) throws SQLException {
checkValidRow();
checkValidIndex(index);
return currentRow[index - 1];
}
Run Code Online (Sandbox Code Playgroud)
这差不多快了。
另一方面,按列名查找则需要更多工作。列名需要区分大小写,无论您使用小写还是大写进行归一化,或者使用进行不区分大小写的查找,都将产生额外的开销TreeMap。
一个简单的实现可能是这样的:
public Object getObject(String columnLabel) throws SQLException {
return getObject(getIndexByLabel(columnLabel));
}
private int getIndexByLabel(String columnLabel) {
Map<String, Integer> indexMap = createOrGetIndexMap();
Integer columnIndex = indexMap.get(columnLabel.toLowerCase());
if (columnIndex == null) {
throw new SQLException("Column label " + columnLabel + " does not exist in the result set");
}
return columnIndex;
}
private Map<String, Integer> createOrGetIndexMap() throws SQLException {
if (this.indexMap != null) {
return this.indexMap;
}
ResultSetMetaData rsmd = getMetaData();
Map<String, Integer> map = new HashMap<>(rsmd.getColumnCount());
// reverse loop to ensure first occurrence of a column label is retained
for (int idx = rsmd.getColumnCount(); idx > 0; idx--) {
String label = rsmd.getColumnLabel(idx).toLowerCase();
map.put(label, idx);
}
return this.indexMap = map;
}
Run Code Online (Sandbox Code Playgroud)
根据数据库的API和可用的语句元数据,可能需要其他处理才能确定查询的实际列标签。根据成本,这可能仅在实际需要时才确定(当按名称访问列标签时,或在检索结果集元数据时)。换句话说,的成本createOrGetIndexMap()可能会很高。
但是,即使该费用可以忽略不计(例如,从数据库服务器准备语句的语句包括列标签),将列标签映射到索引然后按索引检索的开销显然要比直接按索引检索的开销高。
驱动程序甚至可以每次都遍历结果集元数据,并使用标签匹配的第一个。这可能比为具有少量列的结果集构建和访问哈希图便宜,但是成本仍然高于按索引直接访问。
正如我所说的,这是一个笼统的概括,但是如果这种方法(按名称查找索引,然后按索引检索)不是大多数JDBC驱动程序的工作方式,我会感到惊讶,这意味着我希望按索引查找通常会更快。
快速浏览一些驱动程序,情况如下:
我不知道JDBC驱动程序,按列名进行检索的代价是相等的,甚至更便宜。
Luk*_*der 12
在制作jOOQ的早期,我考虑过ResultSet通过索引或通过名称访问 JDBC 值的两种选择。我选择通过索引访问事物的原因如下:
并非所有 JDBC 驱动程序实际上都支持按名称访问列。我忘记了哪些没有,如果仍然没有,因为我已经 13 年没有再接触过 JDBC 的 API 的那部分了。但有些人没有,这对我来说已经是一个阻碍。
\n此外,在支持列名的那些中,列名有不同的语义,主要有两种,即 JDBC 所称的:
\n\n尽管我认为意图很明确,但上述两者的实现存在很多含糊之处:
\nTITLE如果投影表达式是BOOK.TITLE AS XX如果投影表达式是BOOK.TITLE AS X因此,名称/标签的含糊性已经非常令人困惑和令人担忧。一般来说,ORM 似乎不应该依赖它,尽管在 Hibernate 的情况下,人们可以说 Hibernate 控制着大多数正在生成的 SQL,至少是为获取实体而生成的 SQL。但是,如果用户编写 HQL 或本机 SQL 查询,我将不愿意依赖名称/标签 - 至少不ResultSetMetaData首先在 中查找内容。
在 SQL 中,顶层有不明确的列名是完全可以的,例如:
\nSELECT id, id, not_the_id AS id\nFROM book\nRun Code Online (Sandbox Code Playgroud)\n这是完全有效的 SQL。您不能将此查询嵌套为派生表,其中不允许有歧义,但在顶层SELECT可以。现在,您将如何处理ID顶层的那些重复标签?当按名称访问事物时,您无法确定会得到哪一个。前两个可能是相同的,但第三个则非常不同。
明确区分列的唯一方法是通过索引,该索引是唯一的:1, 2, 3。
当时我也尝试过表演。我不再有基准测试结果,但快速编写另一个基准测试很容易。在下面的基准测试中,我在 H2 内存实例上运行一个简单的查询,并使用ResultSet访问内容:
结果是惊人的:
\nBenchmark Mode Cnt Score Error Units\nJDBCResultSetBenchmark.indexAccess thrpt 7 1130734.076 \xc2\xb1 9035.404 ops/s\nJDBCResultSetBenchmark.nameAccess thrpt 7 600540.553 \xc2\xb1 13217.954 ops/s\nRun Code Online (Sandbox Code Playgroud)\n尽管基准测试在每次调用时运行整个查询,但通过索引的访问速度几乎是两倍!你可以看一下H2的代码,它是开源的。它执行此操作(版本 2.1.212):
\nBenchmark Mode Cnt Score Error Units\nJDBCResultSetBenchmark.indexAccess thrpt 7 1130734.076 \xc2\xb1 9035.404 ops/s\nJDBCResultSetBenchmark.nameAccess thrpt 7 600540.553 \xc2\xb1 13217.954 ops/s\nRun Code Online (Sandbox Code Playgroud)\n所以。有一个带有大写字母的哈希图,并且每次查找也执行大写字母。至少,它在准备好的语句中缓存了映射,所以:
\n因此,对于非常大的结果集,它可能不再那么重要,但对于小结果集,它肯定很重要。
\n像 Hibernate 或jOOQ这样的 ORM控制着大量 SQL 和结果集。它确切地知道哪一列在什么位置,这项工作在生成 SQL 查询时就已经完成了。因此,当结果集从数据库服务器返回时,绝对没有理由进一步依赖列名。每个值都将位于预期位置。
\n在 Hibernate 中使用列名肯定是历史性的事情。这可能也是他们用来生成这些不太可读的列别名的原因,以确保每个别名都是明确的。
\n无论现实世界(非基准)查询中的实际收益如何,这似乎都是一个明显的改进。即使改进只有 2%,也是值得的,因为它会影响每个基于 Hibernate 的应用程序的每个查询执行。
\nprivate int getColumnIndex(String columnLabel) {\n checkClosed();\n if (columnLabel == null) {\n throw DbException.getInvalidValueException("columnLabel", null);\n }\n if (columnCount >= 3) {\n // use a hash table if more than 2 columns\n if (columnLabelMap == null) {\n HashMap<String, Integer> map = new HashMap<>();\n // [ ... ]\n\n columnLabelMap = map;\n if (preparedStatement != null) {\n preparedStatement.setCachedColumnLabelMap(columnLabelMap);\n }\n }\n Integer index = columnLabelMap.get(StringUtils.toUpperEnglish(columnLabel));\n if (index == null) {\n throw DbException.get(ErrorCode.COLUMN_NOT_FOUND_1, columnLabel);\n }\n return index + 1;\n }\n // [ ... ]\nRun Code Online (Sandbox Code Playgroud)\n
| 归档时间: |
|
| 查看次数: |
263 次 |
| 最近记录: |