首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Hibernate 性能調優實戰 (下):HQL、抓取策略、二級緩存、批量處理與增強標識符生成器

     編輯:Java 相關知識

4.6 HQL 調優

4.6.1 索引調優:少而精

WHERE 子句決定索引選擇。常見錯誤:每新增 WHERE 就建新索引。索引帶來寫入開銷,應用最少索引覆蓋最多查詢。

策略:收集所有搜索條件 → 後端剖析工具分析 SQL → 分類構建最小索引集。嘗試向 WHERE 添加謂語以復用現有復合索引。

案例:三類搜索器共用兩個復合索引

  • UI 搜索器 1:unexpectedFlag + dealStatus + tradeDate + isold
  • UI 搜索器 2:業務隱含 unexpectedFlag/dealStatus 值 + tradeDate (設為必填)
  • 後台守護進程:isold + participantCode + transactionType

構建索引 1:(unexpectedFlag, dealStatus, tradeDate, isold) — 兩 UI 共用 (順序關鍵)
構建索引 2:(isold, participantCode, transactionType) — 後台專用

4.6.2 綁定參數 vs 字符串拼接

場景推薦理由
OLTP (高頻增刪改、小結果集)綁定參數解析一次、復用執行計劃、省 CPU/內存
數據倉庫 (低頻查詢、大結果集)字符串拼接確定最優執行計劃 (如全表掃描 vs 索引) 比省解析更重要

Oracle 9i+ 首次綁定時探測值生成計劃,後續復用。若 OLTP 確知不同值需同計劃,綁定參數最優。

4.6.3 聚合與排序:下推數據庫

推薦在數據庫層做 GROUP BY/ORDER BY:數據庫優化器更擅長、節省網絡帶寬、跨數據庫可移植。除非業務規則極其特殊 HQL 不支持。

4.6.4 覆蓋抓取策略

詳見 4.7.1。

4.6.5 本地查詢:慎用

HQL 允許傳遞 Native SQL,但跨數據庫不可移植,非必要勿用。

4.7 抓取策略調優

4.7.1 覆蓋抓取策略:按用例定制

兩處定義:映射元數據 (默認) + HQL/Criteria (覆蓋)。

  • 主流程偶爾需關聯 → 默認 lazy/proxy,覆蓋為 join fetch (HQL/Criteria)
  • 主流程多數需關聯 → 默認 eager/join,覆蓋為 lazy (Criteria 支持,HQL 不支持)

4.7.2 N+1 問題:模式還是反模式?

select fetching 導致 N+1。兩種場景可視為模式:

  1. 不確定用戶是否訪問關聯:不訪問則贏,訪問則付出 N 次 select
  2. 一對多鏈長 (A→B→C…):join fetch 導致父對象重複、數據量暴增、會話緩存/GC 壓力大

DISTINCT/Set 去重在 Hibernate 層而非數據庫層。若測試證明 N+1 更優,可用:

  • 批量抓取:<set batch-size="10"> → WHERE id IN (?,?,...) 10 個一批,N 次降為 N/10
  • Subselect 抓取:fetch="subselect" → 子查詢 1 次搞定,前提是父查詢開銷低
  • 二級緩存:穩定只讀關聯數據緩存消除 N+1 (見 4.8.1 案例)

批量/Subselect 對比示例

<set name="pojoBs" fetch="select" batch-size="10">  -- 批量
<set name="pojoBs" fetch="subselect">           -- Subselect
-- 批量 SQL: SELECT ... FROM pojoB WHERE pojoa_id IN (?,?,?,?,?,?,?,?,?,?)
-- Subselect SQL: SELECT ... FROM pojoB WHERE pojoa_id IN (SELECT id FROM pojoA WHERE ...)

4.7.3 延遲屬性抓取:遺留表救星

除非遺留表包含大量無用列,否則不推薦 (分組帶來額外 SQL)。優先重設計領域對象分離關注點,或用 HQL/Criteria 投影 (select new DTO(...)) 獲取所需列。

4.8 二級緩存調優

4.8.1 基於 CacheProvider 的緩存機制 (經典模式,仍主流)

3.3+ 推薦 RegionFactory,但多數緩存提供商 (OSCache、SwarmCache、Coherence、GigaSpaces) 僅支持舊 API。舊 API 核心:

  • CacheConcurrencyStrategy 四種實現對應 usage 屬性
  • EntityUpdate/Delete/InsertAction 觸發緩存同步

三種併發模式

Usage模式特點集群更新策略
read-only只讀最簡單、最高效、集群安全無更新
read-write非事務感知讀寫需緩存鎖、推(push)更新、性能倒數第二更新後推送新值到所有節點
nonstrict-read-write非事務感知讀寫 (寬鬆)無鎖、拉(pull)失效、性能第二好失效所有副本,下次讀取重載
transactional事務感知 (JTA)數據庫+緩存同一 JTA 事務、兩階段提交、性能最差事務內更新緩存

調用序列核心差異

  • read-write:軟鎖 → 更新 DB → 更新緩存 (推)
  • nonstrict-read-write:更新 DB → 清除緩存 (拉),為安全事後再清一次
  • transactional:JTA 事務中更新 DB → 同事務更新緩存;軟鎖 → 更新 DB → 事務提交後更新緩存 / 回滾釋放鎖

後兩種異步序列仍能保證 read committed 級一致性 (歸功軟鎖/悲觀清除)。

選擇建議

  1. 只讀引用數據 → read-only (最優)
  2. 極想要 DB+緩存同事務 → transactional (性能最差,慎用)
  3. 讀多寫少/低併發 → nonstrict-read-write (拉策略輕量,性能次優)
  4. 讀寫均衡/高併發 → read-write (需鎖、推策略重,性能較差)

案例:ISO 收費類型三關聯只讀緩存

<class name="IsoChargeType">
    <many-to-one name="estimateMethod" fetch="join" lazy="false"/>
    <many-to-one name="allocationMethod" fetch="join" lazy="false"/>
    <many-to-one name="chargeTypeCategory" fetch="join" lazy="false"/>
</class>

問題:所有用戶強制加載三關聯。優化:改 lazy="true" + 開啟三關聯 read-only 二級緩存,或保持 join fetch 但受益於引用數據緩存。兩法均能提升全局性能。

4.8.2 RegionFactory 新舊 API 映射

新 (RegionFactory)舊 (CacheProvider)
RegionFactoryCacheProvider
RegionCache
EntityRegionAccessStrategyCacheConcurrencyStrategy
CollectionRegionAccessStrategyCacheConcurrencyStrategy

理解舊模型助快速掌握新模型,<cache usage="..."> 配置共用。

4.9 批量數據加載優化

關鍵設置

hibernate.jdbc.fetch_size = 10  -- Oracle 建議經驗值,慢網絡更關鍵
hibernate.jdbc.batch_size = 20  -- 批量插入/更新

必須關閉緩存:批量加載通常一次性,大量數據入緩存會觸發頻繁 GC。

案例:50 萬行分段加載優化記

  • 原代碼:query.list() + 循環 setStatus() + Spring 聲明式事務 → 10 分鐘、頻繁 OOM、GC 暴增
  • 問題識別:會話/二級緩存導致 OOM、未設 fetch_size、循環產生 50 萬 update SQL
  • 優化:關閉二級緩存、設 fetch_size、DML 風格更新 (UPDATE ... SET status='Inprocess' WHERE ...) → 6 分鐘
  • 終極優化:DML 前 session.clear() 清空會話緩存 → 4 分鐘 (純加載耗時)

Spring 2.5 不支持無狀態會話,故採用上述變通方案。

4.10 SQL 生成調優

4.10.1 N+1 抓取

優先 join fetch,不適用時用 subselect/batch/lazy 減少 SQL。

4.10.2 Insert + Update 雙重打擊

單向 one-to-many (ElectricityDeal → DealCharge) 默認 not-null="false" update="true":

  1. 1 條 Deal INSERT
  2. 10 條 Charge INSERT (無外鍵)
  3. 10 條 Charge UPDATE (回填外鍵)

修復:<key column="DEAL_KEY" not-null="true" update="false"/> → INSERT 直接含外鍵,消除 10 條 UPDATE。或改雙向 many-to-one 由多端維護關聯。

4.10.3 更新前 SELECT

select-before-update="true":對瞬時/分離對象產生額外 SELECT,但避免無異動 UPDATE。權衡:屬性少不值得,大表/遺留表配合 dynamic-update 開啟。

4.10.4 級聯刪除下推數據庫

<key on-delete="cascade"/> + DBA 開啟 ON CASCADE DELETE 約束 → 刪父時數據庫自動刪子,省 100 條 DELETE。風險:繞過 Hibernate 版本樂觀鎖,誤刪父級連帶災難,多數 DBA 拒絕開啟。

4.10.5 增強序列標識符生成器

Oracle 序列每次 SELECT NEXTVAL FROM DUAL,網絡慢時成瓶頸。3.2.3+ SequenceStyleGenerator 提供兩優化器:

優化器HiLo 算法序列增量安全性
hiloHi = 組號 × increment_size1序列專用,其他應用需同邏輯
pooledHi = 序列值 (直接)increment_size其他應用同用序列也安全

組內值用完才訪問序列。increment_size=100 時每 100 次訪問一次 DB。崩潰丟失組內值通常可接受 (Oracle 自帶序列緩存崩潰也丟)。

配置示例

<id name="key" column="ID">
    <generator class="org.hibernate.id.enhance.SequenceStyleGenerator">
        <param name="sequence_name">SEQ_ELECTRICITY_DEALS</param>
        <param name="initial_value">0</param>
        <param name="increment_size">100</param>
        <param name="optimizer">pooled</param>
    </generator>
</id>

5. 總結

本文覆蓋 Hibernate 調優核心戰場:HQL/索引/綁定參數、抓取策略覆蓋/N+1 多種解法、二級緩存併發模式選擇/只讀引用數據緩存、批量加載 fetch_size/會話清理/DML、SQL 生成優化 (Insert+Update/Select-before-update/級聯刪除/增強序列生成器)。文中案例均來自實戰,可直接參考應用。

補充:Hibernate 可整合 In-Memory Data Grid (Oracle Coherence、GigaSpaces IMDG) 達到毫秒級響應。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved