我有一个简单的查询
@Query(value = "select * from some_table where consumer_id=:consumerId and store_id=:storeId and cancelled_at is null", nativeQuery = true)
fun checkIfNewConsumer(consumerId: BigInteger, storeId: BigInteger): List<SomeClass?>
当我使用针对超过3000万行的表的解释直接运行查询时
Index Scan using select_index on some_table (cost=0.56..8.59 rows=1 width=86) (actual time=0.015..0.015 rows=0 loops=1)
Index Cond: ((consumer_id = 1234) AND (store_id = 4) AND (cancelled_at IS NULL))
Planning time: 0.130 ms
Execution time: 0.042 ms
当我使用spring boot通过请求运行相同的查询时:
{"Plan"=>{"Total Cost"=>1317517.92, "Relation Name"=>"some_table", "Parallel Aware"=>"?", "Filter"=>"?", "Alias"=>"some_table", "Node Type"=>"Seq Scan", "Plan Width"=>86, "Startup Cost"=>0.0, "Plan Rows"=>912}}
Execution time: 9613 ms
上面的弹簧靴计划来自新遗物。 如您所见,对于每个查询,默认为 Seq scan ,而不是索引扫描。假设它是数据库(没有骰子),我已经进行了真空分析,我尝试了查询的变体,没有骰子。它总是在plsql,通过spring的borks看起来很完美。
任何建议都将受到高度赞赏。
编辑2:潜在解决方案
我们发现,通过禁用预准备语句,将?preferQueryMode=simple
添加到您的连接网址:jdbc:postgresql://localhost:5432/postgres?preferQueryMode=simple
获取查询以使用索引扫描。
我们需要了解如何?为什么?为什么现在?
编辑1:技术堆栈
编辑:解决方案@Vlad Mihalcea
除非你完全确定它意味着什么,否则请不要使用preferQueryMode = simple。显然,您的问题在https://gist.github.com/vlsi/df08cbef370b2e86a5c1中有所描述。我猜你在数据库中有BigInt,在Kotlin代码中有BigInteger。你能在Kotlin中使用Long吗?
-Vladimir Sitnikov
答案 0 :(得分:2)
由于PostgreSQL不需要任何执行计划缓存,PreparedStatement(s)
实际上是模拟的,直到达到给定的执行阈值(例如5),我认为这是你在这里遇到的索引选择性问题。
如果此查询仅返回少量记录,则数据库将使用索引。
如果此查询将返回大量记录,则数据库将不使用索引,因为随机访问页读取的成本将高于顺序扫描的成本。
所以,这可能是你在这里使用不同的绑定参数值集。
此外,在pgsql上,解释计划不会考虑将所有记录发送到JDBC驱动程序的网络开销。但是,这是对您的问题的补充,而不是实际的根本原因。
现在,要确切了解实际执行计划,请尝试在PostgreSQL中启用auto_explain
模式。
或者,您可以编写一个运行查询的测试方法,如下所示:
List<Object[]> executionPlanLines = doInJPA(entityManager -> {
try(Stream<Object[]> postStream = entityManager
.createNativeQuery(
"EXPLAIN ANALYZE " +
"select * from some_table where consumer_id=:consumerId and store_id=:storeId and cancelled_at is null ")
.setParameter("consumerId", consumerId)
.setParameter("storeId", storeId)
.unwrap(Query.class)
.stream()
) {
return postStream.collect( Collectors.toList() );
}
});
LOGGER.info( "Execution plan: {}",
executionPlanLines
.stream()
.map( line -> (String) line[0] )
.collect( Collectors.joining( "\n" ) )
);
这样,您就可以看到在生产中运行的实际执行计划。
答案 1 :(得分:1)
请不要使用preferQueryMode=simple
,除非您完全确定其含义(例如,处理逻辑复制流可能会有所帮助)。
显然,您的问题在https://gist.github.com/vlsi/df08cbef370b2e86a5c1中有所描述。我猜您在数据库中有bigint
,在Kotlin代码中有BigInteger
。您可以在Kotlin中使用Long
吗?
以防万一:在PostgreSQL中,bigint
意味着int8
,因此应在应用程序中使用Long
。
替代选项是添加如下所示的显式类型转换:consumer_id=cast(:consumerId as bigint) and store_id=cast(:storeId as bigint)
。
问题与“与数值相比的字符列”相同,但是,这里的区别更加微妙(int8与数字)