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

时间:2017-11-22 00:59:47

标签: sql postgresql hibernate spring-boot spring-data-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<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的bo​​rks看起来很完美。

任何建议都将受到高度赞赏。

编辑2:潜在解决方案

我们发现,通过禁用预准备语句,将?preferQueryMode=simple添加到您的连接网址:jdbc:postgresql://localhost:5432/postgres?preferQueryMode=simple获取查询以使用索引扫描。

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

编辑1:技术堆栈

  • Spring boot 2.0M5
  • Kotlin
  • PostgreSQL 9.6.2

编辑:解决方案@Vlad Mihalcea

  

除非你完全确定它意味着什么,否则请不要使用preferQueryMode = simple。显然,您的问题在https://gist.github.com/vlsi/df08cbef370b2e86a5c1中有所描述。我猜你在数据库中有BigInt,在Kotlin代码中有BigInteger。你能在Kotlin中使用Long吗?

-Vladimir Sitnikov

2 个答案:

答案 0 :(得分:2)

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

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

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

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

  1. 您在pgsql控制台中提供的内容具有高度选择性,因此您可以获得索引扫描。
  2. 您在运行时发送的内容可能不同,因此您将获得顺序扫描。
  3. 此外,在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与数字)