entityManager.createQuery() 花费大量时间来构建查询和绑定参数。性能受影响
entityManager.createQuery() taking lot of time to build query and bind the parameters. Performance affected
我们正在使用 Spring JPA 条件查询 (javax.persistence.criteria.CriteriaQuery) 从数据库中获取数据。我们使用 javax.persistence.criteria.Predicate 来构建谓词。我们在一个查询中有 1500 'OR' 个谓词。每个谓词有 6 'AND' 个谓词。
SELECT (*) FROM TABLE_ABC as T1 WHERE (t1.column1 = 'c11' AND
t1.column2 = 'c12' AND t1.column3 = 'c13' AND t1.column4 = 'c14' AND
t1.column5 = 'c15')
OR
(t1.column1 = 'c21' AND t1.column2 = 'c22'
AND t1.column3 = 'c23' AND t1.column4 = 'c24' AND t1.column5 = 'c25')
OR
(t1.column1 = 'c31' AND t1.column2 = 'c32'
AND t1.column3 = 'c33' AND t1.column4 = 'c34' AND t1.column5 = 'c35').....
之前我们使用 "org.hibernate.Criteria" 并使用 'Conjuction' 和 'Disjunction' 构建相同的查询。这种方法很有效。由于 "org.hibernate.Criteria" 被删除,我们将转向 javax-criteriaquery 包。我们正面临性能的大幅下降。日志的向下钻取表明在步骤
中消耗的时间较多
=> entityManager.createQuery(),执行以下操作
- CriteriaCompiler.compile
- CriteriaQueryImpl$1.buildCompiledQuery
- 标准编译器$1$1.bind
这些操作比较耗时。
是否有任何解决方案可以使这些执行速度更快?
'javax.persistence.criteria.CriteriaQuery' 是前进的方向吗?
请帮忙!
请看下面的代码:
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED)
public getData(List<DataDAO> dataReqList) {
{
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<DataReq> criteriaQuery = builder.createQuery(DataReq.class);
Root<DataReq> dataReqRoot = criteriaQuery.from(DataReq.class);
Predicate[] predicateArr = new Predicate[dataReqList.size()];
for (DataDAO dataReq : dataReqList) {
predicateArr[i] = builder.and(
builder.equal(dataReqRoot.get(TEST_S), dataReq.getS()),
builder.equal(dataReqRoot.get(TEST_T2), dataReq.getT2()),
builder.equal(dataReqRoot.get(K1), dataReq.getK1()),
builder.equal(dataReqRoot.get(K2), dataReq.getK2()),
builder.equal(dataReqRoot.get(TEST_P), dataReq.getP()),
builder.equal(dataReqRoot.get(TEST_T1),
dataReq.getT1(),
builder.equal(dataReqRoot.get(TEST_I), dataReq.getI()));
i++;
}
List<Data> dataResultList = getResultList(builder, criteriaQuery, predicateArr);
}
private List<Data> getResultList(CriteriaBuilder builder,
CriteriaQuery<DataReq> criteriaQuery, Predicate[] predicateArr) {
criteriaQuery.where(builder.or(predicateArr));
TypedQuery<DataReq> query = entityManager.createQuery(criteriaQuery);
List<DataReq> dataReqList = null;
try {
dataReqList = query.getResultList();
} catch(Exception e) {
...
}
return convertToData(dataReqList);
}
使用 "org.hibernate.Criteria" 并使用 'Conjuction' 和 'Disjunction' 的相同查询在几毫秒内非常有效。
对于上下文,根据您使用的数据库,这就像一个带有行值表达式的动态 IN
谓词。如果支持,你也可以这样写:
WHERE (t1.column1, t1.column2, t1.column3, t1.column4, t1.column5, t1.column6) IN (
('c11', 'c12', 'c13', 'c14', 'c15', 'c16'),
('c21', 'c22', 'c23', 'c24', 'c25', 'c26'),
...
)
如此长的 IN
列表不仅会在生成动态 SQL 的客户端库中产生问题,而且还会在服务器端产生问题。您提到了绑定变量,也许您使用的旧 API 毕竟没有使用绑定变量,而是将所有值内联到查询中。我已经看到在 Oracle 中对于大量参数的表现要好得多,所以这是 inline values might be better than bind variables.
的情况之一
由于您使用的是 Hibernate,您可以尝试启用
<property name="hibernate.criteria.literal_handling_mode" value="bind"/>
见HHH-9576 and
使用数组可能更好的解决方案
以上内容(可能)有助于恢复您以前体验过的性能,但根据您的 IN
列表大小,可能会有更好的解决方案。我已经在博客上介绍了您可以使用 arrays instead of individual bind values, in case you're using Oracle or PostgreSQL.
的替代方案
使用临时 tables
可能更好的解决方案
我经常看到的另一个选择是使用临时 table 形式的(假设 Oracle):
CREATE GLOBAL TEMPORARY TABLE predicates (
column1 VARCHAR2(100),
column2 VARCHAR2(100),
column3 VARCHAR2(100),
column4 VARCHAR2(100),
column5 VARCHAR2(100),
column6 VARCHAR2(100)
)
然后,在 运行 您的查询之前,将所有不同的谓词值批量插入 table 然后半连接它:
WHERE (t1.column1, t1.column2, t1.column3, t1.column4, t1.column5, t1.column6) IN (
SELECT column1, column2, column3, column4, column5, column6
FROM predicates
)
如果您没有临时 tables,您可以尝试使用普通的 tables,并向其中添加一个 transaction_id 列,在您完成后手动清理其内容查询。
我们正在使用 Spring JPA 条件查询 (javax.persistence.criteria.CriteriaQuery) 从数据库中获取数据。我们使用 javax.persistence.criteria.Predicate 来构建谓词。我们在一个查询中有 1500 'OR' 个谓词。每个谓词有 6 'AND' 个谓词。
SELECT (*) FROM TABLE_ABC as T1 WHERE (t1.column1 = 'c11' AND
t1.column2 = 'c12' AND t1.column3 = 'c13' AND t1.column4 = 'c14' AND
t1.column5 = 'c15')
OR
(t1.column1 = 'c21' AND t1.column2 = 'c22'
AND t1.column3 = 'c23' AND t1.column4 = 'c24' AND t1.column5 = 'c25')
OR
(t1.column1 = 'c31' AND t1.column2 = 'c32'
AND t1.column3 = 'c33' AND t1.column4 = 'c34' AND t1.column5 = 'c35').....
之前我们使用 "org.hibernate.Criteria" 并使用 'Conjuction' 和 'Disjunction' 构建相同的查询。这种方法很有效。由于 "org.hibernate.Criteria" 被删除,我们将转向 javax-criteriaquery 包。我们正面临性能的大幅下降。日志的向下钻取表明在步骤
中消耗的时间较多=> entityManager.createQuery(),执行以下操作
- CriteriaCompiler.compile
- CriteriaQueryImpl$1.buildCompiledQuery
- 标准编译器$1$1.bind
这些操作比较耗时。
是否有任何解决方案可以使这些执行速度更快? 'javax.persistence.criteria.CriteriaQuery' 是前进的方向吗?
请帮忙!
请看下面的代码:
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED)
public getData(List<DataDAO> dataReqList) {
{
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<DataReq> criteriaQuery = builder.createQuery(DataReq.class);
Root<DataReq> dataReqRoot = criteriaQuery.from(DataReq.class);
Predicate[] predicateArr = new Predicate[dataReqList.size()];
for (DataDAO dataReq : dataReqList) {
predicateArr[i] = builder.and(
builder.equal(dataReqRoot.get(TEST_S), dataReq.getS()),
builder.equal(dataReqRoot.get(TEST_T2), dataReq.getT2()),
builder.equal(dataReqRoot.get(K1), dataReq.getK1()),
builder.equal(dataReqRoot.get(K2), dataReq.getK2()),
builder.equal(dataReqRoot.get(TEST_P), dataReq.getP()),
builder.equal(dataReqRoot.get(TEST_T1),
dataReq.getT1(),
builder.equal(dataReqRoot.get(TEST_I), dataReq.getI()));
i++;
}
List<Data> dataResultList = getResultList(builder, criteriaQuery, predicateArr);
}
private List<Data> getResultList(CriteriaBuilder builder,
CriteriaQuery<DataReq> criteriaQuery, Predicate[] predicateArr) {
criteriaQuery.where(builder.or(predicateArr));
TypedQuery<DataReq> query = entityManager.createQuery(criteriaQuery);
List<DataReq> dataReqList = null;
try {
dataReqList = query.getResultList();
} catch(Exception e) {
...
}
return convertToData(dataReqList);
}
使用 "org.hibernate.Criteria" 并使用 'Conjuction' 和 'Disjunction' 的相同查询在几毫秒内非常有效。
对于上下文,根据您使用的数据库,这就像一个带有行值表达式的动态 IN
谓词。如果支持,你也可以这样写:
WHERE (t1.column1, t1.column2, t1.column3, t1.column4, t1.column5, t1.column6) IN (
('c11', 'c12', 'c13', 'c14', 'c15', 'c16'),
('c21', 'c22', 'c23', 'c24', 'c25', 'c26'),
...
)
如此长的 IN
列表不仅会在生成动态 SQL 的客户端库中产生问题,而且还会在服务器端产生问题。您提到了绑定变量,也许您使用的旧 API 毕竟没有使用绑定变量,而是将所有值内联到查询中。我已经看到在 Oracle 中对于大量参数的表现要好得多,所以这是 inline values might be better than bind variables.
由于您使用的是 Hibernate,您可以尝试启用
<property name="hibernate.criteria.literal_handling_mode" value="bind"/>
见HHH-9576 and
使用数组可能更好的解决方案
以上内容(可能)有助于恢复您以前体验过的性能,但根据您的 IN
列表大小,可能会有更好的解决方案。我已经在博客上介绍了您可以使用 arrays instead of individual bind values, in case you're using Oracle or PostgreSQL.
使用临时 tables
可能更好的解决方案我经常看到的另一个选择是使用临时 table 形式的(假设 Oracle):
CREATE GLOBAL TEMPORARY TABLE predicates (
column1 VARCHAR2(100),
column2 VARCHAR2(100),
column3 VARCHAR2(100),
column4 VARCHAR2(100),
column5 VARCHAR2(100),
column6 VARCHAR2(100)
)
然后,在 运行 您的查询之前,将所有不同的谓词值批量插入 table 然后半连接它:
WHERE (t1.column1, t1.column2, t1.column3, t1.column4, t1.column5, t1.column6) IN (
SELECT column1, column2, column3, column4, column5, column6
FROM predicates
)
如果您没有临时 tables,您可以尝试使用普通的 tables,并向其中添加一个 transaction_id 列,在您完成后手动清理其内容查询。