20

私は2つのテーブルEmployeeとDepartmentを持っています。以下は両方のエンティティクラスです

Department.java
@Entity
@Table(name = "DEPARTMENT")
public class Department {
    @Id
    @Column(name = "DEPARTMENT_ID")
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer departmentId;
    @Column(name = "DEPARTMENT_NAME")
    private String departmentName;
    @Column(name = "LOCATION")
    private String location;

    @OneToMany(cascade = CascadeType.ALL, mappedBy = "department", orphanRemoval = true)
    @Fetch(FetchMode.SUBSELECT)
    //@Fetch(FetchMode.JOIN)
    private List<Employee> employees = new ArrayList<>();
}


Employee.java
@Entity
@Table(name = "EMPLOYEE")
public class Employee {
    @Id
    @SequenceGenerator(name = "emp_seq", sequenceName = "seq_employee")
    @GeneratedValue(generator = "emp_seq")
    @Column(name = "EMPLOYEE_ID")
    private Integer employeeId;
    @Column(name = "EMPLOYEE_NAME")
    private String employeeName;

    @ManyToOne
    @JoinColumn(name = "DEPARTMENT_ID")
    private Department department;
}

以下は、私が行ったときに発生したクエリですem.find(Department.class, 1);

-- フェッチ モード = fetchmode.join

    SELECT department0_.DEPARTMENT_ID AS DEPARTMENT_ID1_0_0_,
      department0_.DEPARTMENT_NAME    AS DEPARTMENT_NAME2_0_0_,
      department0_.LOCATION           AS LOCATION3_0_0_,
      employees1_.DEPARTMENT_ID       AS DEPARTMENT_ID3_1_1_,
      employees1_.EMPLOYEE_ID         AS EMPLOYEE_ID1_1_1_,
      employees1_.EMPLOYEE_ID         AS EMPLOYEE_ID1_1_2_,
      employees1_.DEPARTMENT_ID       AS DEPARTMENT_ID3_1_2_,
      employees1_.EMPLOYEE_NAME       AS EMPLOYEE_NAME2_1_2_
    FROM DEPARTMENT department0_
    LEFT OUTER JOIN EMPLOYEE employees1_
    ON department0_.DEPARTMENT_ID   =employees1_.DEPARTMENT_ID
    WHERE department0_.DEPARTMENT_ID=?

-- フェッチ モード = fetchmode.subselect

    SELECT department0_.DEPARTMENT_ID AS DEPARTMENT_ID1_0_0_,
      department0_.DEPARTMENT_NAME    AS DEPARTMENT_NAME2_0_0_,
      department0_.LOCATION           AS LOCATION3_0_0_
    FROM DEPARTMENT department0_
    WHERE department0_.DEPARTMENT_ID=?

    SELECT employees0_.DEPARTMENT_ID AS DEPARTMENT_ID3_1_0_,
      employees0_.EMPLOYEE_ID        AS EMPLOYEE_ID1_1_0_,
      employees0_.EMPLOYEE_ID        AS EMPLOYEE_ID1_1_1_,
      employees0_.DEPARTMENT_ID      AS DEPARTMENT_ID3_1_1_,
      employees0_.EMPLOYEE_NAME      AS EMPLOYEE_NAME2_1_1_
    FROM EMPLOYEE employees0_
    WHERE employees0_.DEPARTMENT_ID=?

どちらを優先すべきか知りたかっただけですFetchMode.JOINFetchMode.SUBSELECT?どのシナリオでどれを選択する必要がありますか?

4

4 に答える 4

41

Marmite が参照する SUBQUERY 戦略は、SUBSELECT ではなく、FetchMode.SELECT に関連しています。

fetchmode.subselectについて投稿したコンソール出力は、これが機能するはずの方法ではないため、興味深いものです。

FetchMode.SUBSELECT _

副選択クエリを使用して、追加のコレクションをロードします

休止状態のドキュメント:

1 つの遅延コレクションまたは単一値プロキシをフェッチする必要がある場合、Hibernate はそれらすべてをロードし、サブセレクトで元のクエリを再実行します。これは、バッチ フェッチと同じように機能しますが、断片的な読み込みはありません。

FetchMode.SUBSELECT は次のようになります。

SELECT <employees columns>
FROM EMPLOYEE employees0_
WHERE employees0_.DEPARTMENT_ID IN
(SELECT department0_.DEPARTMENT_ID FROM DEPARTMENT department0_)

この 2 番目のクエリでは、ある部門に属するすべての従業員がメモリに取り込まれることがわかります(つまり、employee.department_id は null ではありません)。最初のクエリで取得した部門でなくても問題ありません。したがって、従業員のテーブルが大きい場合、誤ってデータベース全体をメモリにロードする可能性があるため、これは大きな問題になる可能性があります。

ただし、FetchMode.SUBSELECT は、FecthMode.SELECT の N+1 クエリと比較して 2 つのクエリしか必要としないため、クエリの数を大幅に削減します。

FetchMode.JOIN が作成するクエリはさらに少なく、たった 1 つだと考えているかもしれません。確かにそうですが、データが重複し、レスポンスが重くなります。

単一値のプロキシを JOIN で取得する必要がある場合、クエリは次のものを取得できます。

+---------------+---------+-----------+
| DEPARTMENT_ID | BOSS_ID | BOSS_NAME |
+---------------+---------+-----------+
|             1 |       1 | GABRIEL   |
|             2 |       1 | GABRIEL   |
|             3 |       2 | ALEJANDRO |
+---------------+---------+-----------+

上司が複数の部門を指揮する場合、上司の従業員データが複製され、帯域幅にコストがかかります。

遅延コレクションを JOIN でフェッチする必要がある場合、クエリは次のものを取得できます。

+---------------+---------------+-------------+
| DEPARTMENT_ID | DEPARTMENT_ID | EMPLOYEE_ID |
+---------------+---------------+-------------+
|             1 | Sales         | GABRIEL     |
|             1 | Sales         | ALEJANDRO   |
|             2 | RRHH          | DANILO      |
+---------------+---------------+-------------+

部門データに複数の従業員が含まれている場合 (自然なケース)、部門データは複製されます。帯域幅のコストがかかるだけでなく、Department オブジェクトが重複して重複するため、SET またはDISTINCT_ROOT_ENTITYを使用して重複を排除する必要があります。

ただし、Markus Winandが言うように、多くの場合、低レイテンシーの pos での重複データは適切なトレードオフです。

SQL 結合は、多くのネットワーク通信を回避するため、同じインデックス ルックアップを実行しますが、ネストされた選択アプローチよりも効率的です。販売ごとに従業員属性が重複するため、転送されるデータの合計量が大きい場合は、さらに高速になります。これは、パフォーマンスの 2 つの側面 (応答時間とスループット) によるものです。コンピュータ ネットワークでは、これらをレイテンシと帯域幅と呼んでいます。帯域幅は応答時間にわずかな影響しか与えませんが、遅延は大きな影響を与えます。つまり、データベースの往復回数は、転送されるデータの量よりも応答時間にとって重要です。

したがって、SUBSELECT の使用に関する主な問題は、制御が難しく、エンティティのグラフ全体をメモリにロードする可能性があることです。INサブクエリは、外部クエリによって取得された ID によってフィルター処理されます)。

Hibernate: 
    select ...
    from mkyong.stock stock0_

Hibernate: 
    select ...
    from mkyong.stock_daily_record stockdaily0_ 
    where
        stockdaily0_.STOCK_ID in (
            ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
        )

(バッチ サイズが非常に大きいバッチ フェッチが SUBSELECT のように機能するが、テーブル全体のロードの問題がない場合は、興味深いテストになる可能性があります)

さまざまなフェッチ戦略と SQL ログを示すいくつかの投稿 (非常に重要):

概要:

  • JOIN: N+1 クエリの主要な問題を回避しますが、データが重複して取得される可能性があります。
  • SUBSELECT: N+1 も回避し、データを複製しませんが、関連付けられた型のすべてのエンティティをメモリに読み込みます。

テーブルはascii-tablesを使用して作成されました。

于 2016-05-02T13:08:01.047 に答える
9

依存していると言えます...

部門に N 人の従業員がいて、D バイトの情報が含まれており、平均的な従業員は E バイトで構成されているとします。(バイトは、属性の長さとオーバーヘッドの合計です)。

結合戦略を使用して、1 つのクエリを実行し、N * (D + E) データを転送します。

サブクエリ戦略を使用すると、1 + N クエリを実行しますが、D + N*E データのみを転送します。

通常、N が大きい場合、N+1 クエリNO GOであるため、JOIN が優先されます。

ただし、実際には、クエリ数とデータ転送の間のマイレージを確認する必要があります。

Hibernate キャッシングとして他の側面を考慮していないことに注意してください。

従業員テーブルが大きくてパーティション化されている場合、追加の微妙な側面が有効になる可能性があります。インデックス アクセスでのパーティションのプルーニングも考慮に入れられます。

于 2015-10-07T07:18:26.563 に答える
2

プランキー 彼は言った

(1) これはひどく誤解を招くものです。(2) サブセレクトは、データベース全体をメモリにフェッチしません。リンクされた記事は、サブセレクト (3) が親からのページング コマンドを無視する癖 (4) に関するものですが、それでもサブセレクトです。

  1. あなたのコメントの後、私は FetchMode.SUBSELECT について再度調査しましたが、私の答えが完全に正しいわけではないことがわかりました。
  2. これは、メモリに完全にロードされていた各エンティティ (この場合は従業員) のハイドレーションが他の多くのエンティティのハイドレーションを終了するという仮説的な状況でした。本当の問題は、そのテーブルに何千もの行が含まれている場合、サブ選択されているテーブル全体をロードすることです (それらのそれぞれが他のテーブルから他のエンティティを熱心にフェッチしない場合でも)。
  3. 親からのページング コマンドの意味がわかりません。
  4. はい、それはまだ副選択ですが、これで何を指摘しようとしているのかわかりません。

fetchmode.subselect について投稿したコンソール出力は、これが機能するはずの方法ではないため、興味深いものです。

これは本当ですが、複数の Department エンティティが隠蔽されている (複数の従業員コレクションが初期化されていないことを意味する) 場合にのみ、 3.6.10.Finalおよび4.3.8.Finalでテストしました シナリオ2.2 (FetchMode.SUBSELECT hidrating 2 of 3 Departments)および 3.2 (FetchMode.SUBSELECT hidrating all Departments)の場合、SubselectFetch.toSubselectStringは次を返します (Hibernate クラスへのリンクは 4.3.8.Final タグから取得されます)。

select this_.DEPARTMENT_ID from SUBSELECT_DEPARTMENT this_

このサブクエリは、 OneToManyJoinWalker.initStatementStringで終わるwhere 句を作成するために使用されます。

employees0_.DEPARTMENT_ID in (select this_.DEPARTMENT_ID from SUBSELECT_DEPARTMENT this_)

次に、 CollectionJoinWalker.whereStringに where 句が追加され、末尾が

select employees0_.DEPARTMENT_ID as DEPARTMENT3_2_1_, employees0_.EMPLOYEE_ID as EMPLOYEE1_1_, employees0_.EMPLOYEE_ID as EMPLOYEE1_3_0_, employees0_.DEPARTMENT_ID as DEPARTMENT3_3_0_, employees0_.EMPLOYEE_NAME as EMPLOYEE2_3_0_ from SUBSELECT_EMPLOYEE employees0_ where employees0_.DEPARTMENT_ID in (select this_.DEPARTMENT_ID from SUBSELECT_DEPARTMENT this_)

このクエリでは、両方のケースで、すべての従業員が取得され、ハイドレートされています。これは明らかにシナリオ 2.2 の問題です。なぜなら、部門 1 と 2 だけをハイドレートしているのに、それらの部門に属していなくてもすべての従業員 (この場合は部門 3 の従業員) にもハイドレーションを行っているからです。

従業員コレクションが初期化されていない状態で、セッション内でハイドレートされた Department エンティティが 1 つしかない場合、クエリは、eatSleepCode が記述したものと似ています。シナリオ 1.2 を確認する

select subselectd0_.department_id as departme1_2_0_, subselectd0_.department_name as departme2_2_0_, subselectd0_.location as location3_2_0_ from subselect_department subselectd0_ where subselectd0_.department_id=?

FetchStyleから

    /**
     * Performs a separate SQL select to load the indicated data.  This can either be eager (the second select is
     * issued immediately) or lazy (the second select is delayed until the data is needed).
     */
    SELECT,
    /**
     * Inherently an eager style of fetching.  The data to be fetched is obtained as part of an SQL join.
     */
    JOIN,
    /**
     * Initializes a number of indicated data items (entities or collections) in a series of grouped sql selects
     * using an in-style sql restriction to define the batch size.  Again, can be either eager or lazy.
     */
    BATCH,
    /**
     * Performs fetching of associated data (currently limited to only collections) based on the sql restriction
     * used to load the owner.  Again, can be either eager or lazy.
     */
    SUBSELECT

今まで、この Javadoc が何を意味するのかを解決できませんでした:

所有者をロードするために使用される sql 制限に基づく

UPDATE プランキーは次のように述べています。

代わりに、最悪の場合、最初のクエリに where 句がなかった場合にのみ、テーブルをロードするだけです。したがって、結果を制限していて WHERE 条件がない場合、副選択クエリを使用すると予期せずテーブル全体が読み込まれる可能性があると言えます。

これは真実であり、新しいシナリオ 4.2でテストした非常に重要な詳細です。

従業員を取得するために生成されるクエリは次のとおりです。

select employees0_.department_id as departme3_4_1_, employees0_.employee_id as employee1_5_1_, employees0_.employee_id as employee1_5_0_, employees0_.department_id as departme3_5_0_, employees0_.employee_name as employee2_5_0_ from subselect_employee employees0_ where employees0_.department_id in (select this_.department_id from subselect_department this_ where this_.department_name>=?)

where 句内のサブクエリには、元の制限this_.department_name>=?が含まれています。、すべての従業員の負荷を回避します。これは、javadocが意味するものです

所有者をロードするために使用される sql 制限に基づく

FetchMode.JOIN について述べたことと、FetchMode.SUBSELECT との違いはすべて当てはまります (FetchMode.SELECT にも当てはまります)。

于 2017-09-03T23:53:02.063 に答える