簡體   English   中英

Spring JPA查詢始終使用序列掃描而不是索引掃描

[英]Spring JPA query always uses Sequence Scan instead of an Index Scan

我有一個簡單的查詢

@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掃描而不是索引掃描 假設它是數據庫(沒有骰子),我已經進行了真空分析,我嘗試了查詢的變體,沒有骰子。 它總是在plsql,通過spring的bo​​rks看起來很完美。

任何建議都將受到高度贊賞。

編輯2:潛在的解決方案

我們發現通過禁用預處理語句將?preferQueryMode=simple添加到你的連接url: jdbc:postgresql://localhost:5432/postgres?preferQueryMode=simple讓查詢使用索引掃描。

我們需要了解如何? 為什么? 為什么現在?

編輯1:技術堆棧

  • Spring boot 2.0M5
  • 科特林
  • PostgreSQL 9.6.2

編輯:解決方案@Vlad Mihalcea

請不要使用preferQueryMode = simple,除非你完全確定它意味着什么。 顯然,您的問題在https://gist.github.com/vlsi/df08cbef370b2e86a5c1中有所描述。 我猜你在數據庫中有BigInt,在Kotlin代碼中有BigInteger。 你能在Kotlin使用Long嗎?

-Vladimir Sitnikov

由於PostgreSQL不需要執行任何執行計划緩存,並且PreparedStatement(s)實際上是模擬的,直到達到給定的執行閾值(例如5),我認為這是你在這里遇到的索引選擇性問題。

如果此查詢僅返回少量記錄,則數據庫將使用該索引。

如果此查詢將返回大量記錄,則數據庫將不使用索引,因為隨機訪問頁讀取的成本將高於順序掃描的成本。

因此,可能是您在此處使用不同的綁定參數值集。

  1. 你在pgsql控制台中給出的那些是高度選擇性的,因此你得到了索引掃描。
  2. 您在運行時發送的可能不同,因此您將獲得順序掃描。

此外,在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" ) )
);

這樣,您將看到生產中運行的實際執行計划。

請不要使用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與數字相比)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM