すべてのプロダクト
Search
ドキュメントセンター

PolarDB:完全なデータを抽出する方法を最適化する

最終更新日:May 30, 2024

このトピックでは、アプリケーションでコードを使用してデータを効率的に抽出する方法について説明します。

概要

データ抽出とは、コードまたはデータエクスポートツールを使用して、一度にPolarDB-Xから完全なデータを読み取るプロセスです。 次のシナリオでデータを抽出できます。
  • すべてのデータは、データエクスポートツールを使用して下流システムにエクスポートされます。 PolarDB-Xは複数のデータエクスポートツールをサポートしています。
  • PolarDB-Xでデータが処理された場合、または一度に複数のクエリ結果がユーザーに返された場合、外部ツールは使用できません。 この場合、コードを使用してPolarDB-Xの完全なデータを抽出する必要があります。

このトピックでは、PolarDB-Xのコードを使用して効率的にデータを抽出する方法について説明します。 一度に完全なデータを抽出したり、ページクエリを実行したりできます。

完全なデータを抽出する

ほとんどの場合、完全なデータを抽出するSQL文には、テーブルのシャードキーが含まれていません。 フルテーブルスキャンを実行してフルデータを抽出する場合、読み取りデータの量は、データの抽出に必要な時間と正の相関があります。 ヒントを使用して、SQL文を物理シャードに送信し、シャードからデータをプルできます。 これにより、ネットワークまたは接続リソースが節約されます。 次のJavaコードは、ヒントを使用して完全なデータを抽出する方法の例を示しています。 詳細については、「ヒントの使用方法」をご参照ください。

public static void extractData(Connection connection, String logicalTableName, Consumer<ResultSet> consumer)
    SQLExceptionをスロー {

    final String topology = "{0} からトポロジを表示する";
    final String query = "/* + TDDL:NODE({0})*/select * from {1}";

    try (final Statement statement = connection.createStatement()) {
        最終マップ <String, List<String>> partitionTableMap = new LinkedHashMap<>();
        // 指定された論理テーブルのパーティションidと物理テーブル名を取得する
        try (final ResultSet rs = statement.exe cuteQuery(MessageFormat.format(topology, logicalTableName))) {
            while (rs.next()) {
                partitionTableMap.com puteIfAbsent(rs.getString(2), (k) -> new ArrayList<>()).add(rs.getString(3));
            }
        }
        // 各パーティションからデータをシリアルに抽出する
        for (Map.Entry<String, List<String>> entry : partitionTableMap.entrySet()) {
            for (String tableName : entry.getValue()) {
                try (final ResultSet rs=ステートメント
                    . executeQuery(MessageFormat.format(query, entry.getKey(), tableName)) {
                    // データを消費する
                    consumer.accept(rs);
                }
            }
        }
    }
}

ページ化されたクエリの実行

リスト内のデータをユーザーに表示する場合は、ページの読み込みを高速化するために、データをページごとに表示する必要があります。 これにより、過剰な冗長情報が表示されることを防ぐ。 このクエリメソッドは、ページクエリと呼ばれます。 リレーショナルデータベースは、セグメントごとにテーブルのデータを返すことはできません。 ページ化されたクエリを効率的に実行するには、データベースの特性に基づいてクエリ文を記述する必要があります。

次のコードブロックは、MySQLでページ化されたクエリの例を示します。 この例では、pagedクエリはlimit offsetとpageSizeを使用して実行されます。

select * from t_order where user_id = xxx order by gmt_create, id limit offset, pageSize

gmt_create列の値は重複している可能性があります。 order by句に含まれるid列は、結果が正しい順序でソートされるようにするために使用されます。

説明 テーブルサイズが小さい場合、クエリメソッドは期待どおりに機能します。 t_orderテーブルに数十万のレコードが格納されている場合、ページ数の増加によりデータクエリが遅くなります。 速度は、クエリごとに数十ミリ秒に減少し得る。 データ量が数百万レコードに増加すると、データは数秒でテーブルから照会されます。 データ量が増加し続けると、クエリの時間は受け入れられなくなります。
課題分析

user_id列とgmt_create列にローカルセカンダリインデックス (LSI) を作成する場合、WHERE句にはuser_id列のみが指定されているため、一度にスキャンされるデータレコードの総数はoffsetパラメーターとpageSizeパラメーターで指定された値の合計になります。 offsetパラメーターに指定された値が大きくなると、スキャンされたレコードの数はテーブル内のレコードの総数に近くなります。 これにより、クエリに必要な時間が増加します。 分散データベースでは、データノードを追加してテーブルソートのスループットを上げることはできません。

解決策1

ページのクエリ結果は、前のページが終了した時点から開始するように指定できます。 これにより、offsetパラメーターを指定する必要なく、テーブル全体のスキャンを防ぐことができます。 次のコードブロックは、ID列に基づくページ化クエリの例を示しています。

select * from t_order where id > lastMaxId order by id limit pageSize

この例では、クエリされた最初のページに条件が指定されていません。 2番目のページが照会されると、最初のページに対して照会されたid列の最大値が照会条件に指定されます。 このようにして、データベースはindex列のlastMaxIdパラメーターの値を識別し、クエリ結果を返します。 pageSizeパラメーターは、返すレコードの数を指定します。

説明 ID列が主キーまたは一意キーである場合は、ソリューション1を使用してページクエリを実行できます。 しかしながら、この解決策には欠点がある。 ID列に重複する値が含まれている場合、重複する値を含むレコードは返されない場合があります。
解決策2

MySQLでは、Row Constructor Expressionを使用して複数の列の値を比較できます (これはPolarDB-Xにも適用されます) 。

(c2,c3) > (1,1)
に相当します。c2 > 1 OR ((c2 = 1) AND (c3 > 1)) 

次の構文を使用して、ページクエリを実行できます。

select * from t_order
user_id = xxxおよび (gmt_create, id) > (lastMaxGmtCreate, lastMaxId)
order by user_id, gmt_create, id limit pageSize 

最初のページが照会されると、条件は指定されません。 2番目のページが照会されると、最初のページで照会されるgmt_create列とid列の最大値が照会条件に指定されます。 同様のルールが他のページにも有効です。 行構築式を使用して、gmt_create列の重複値を処理できます。

説明 この例では、user_id列とgmt_create列に複合インデックスが作成され、user_id列がorder by句に追加されます。 このように、オプティマイザはインデックス列を使用してデータを並べ替えることができ、クエリのパフォーマンスが向上します。 Row Constructor Expressionにnullが含まれる場合、この式の結果はnullになります。 この場合、行コンストラクター式を含むSQLステートメントでOR式を使用する必要があります。 PolarDB-Xでは、行コンストラクター式がシャードキーのみを含む場合にのみ、行コンストラクター式が列のプルーニングに使用されます。 他のシナリオでは、OR式も必要です。

次のコードブロックは、PolarDB-Xインスタンスでページ化クエリを実行するためのベストプラクティスを提供します。

-- lastMaxGmtCreateがnullではない
select * from t_order
user_id = xxx
と (
      (gmt_create > lastMaxGmtCreate) 
      または ((gmt_create = lastMaxGmtCreate) および (id > lastMaxId))
    )
order by user_id, gmt_create, id limit pageSize

-- lastMaxGmtCreateはnullです
select * from t_order
user_id = xxx
と (
      (gmt_createはnullではありません)
      または (gmt_createはnull、id > lastMaxId)
    )
order by user_id, gmt_create, id limit pageSize