Spring JPA查询总是使用序列扫描而不是索引扫描

我有一个简单的查询

@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 

当我运行查询时,直接对超过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扫描每个查询,而不是索引扫描 。 我已经真空分析,假设它是数据库(没有骰子),我已经尝试变化的查询,没有骰子。 它总是看起来很完美的plsql,通过springborks。

任何意见将不胜感激。

编辑2:潜在的解决方案

我们发现通过禁用准备好的语句添加?preferQueryMode=simple到您的连接url: jdbc:postgresql://localhost:5432/postgres?preferQueryMode=simple得到查询使用索引扫描。

我们需要了解如何? 为什么? 为什么现在?

编辑1:技术堆栈

  • 春季开机2.0M5
  • 科特林
  • PostgreSQL 9.6.2

由于PostgreSQL不需要任何执行计划缓存, PreparedStatement(s)实际上是模拟的,直到达到给定的执行阈值(例如5),我认为这是您在这里面临的索引选择性问题。

如果此查询只返回少量记录,则数据库将使用该索引。

如果此查询将返回大量记录,则数据库将不使用索引,因为随机访问页面读取的成本将高于顺序扫描的成本。

所以,这可能是你在这里使用不同的绑定参数值集合。

  1. 你在pgsql控制台中给出的是高度选择性的,因此你得到索引扫描。
  2. 你在运行时发送的可能是不同的,因此你得到一个顺序扫描。

更多地,在pgsql上,解释计划不会考虑将所有记录发送到JDBC驱动程序的网络开销。 但是,这是对您的问题的补充,而不是实际的根本原因。

现在,要真正确定实际的执行计划,请尝试在PostgreSQL中启用auto_explain模式。

或者,您可以编写一个运行查询的测试方法,如下所示:

 List executionPlanLines = doInJPA(entityManager -> { try(Stream 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" ) ) ); 

这样,你就可以看到在生产中运行的实际执行计划。