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

PolarDB:同時実行性の高いシナリオでの実践と最適化

最終更新日:Jun 05, 2024

このトピックでは、クエリがポイントクエリかどうかを判断する方法と、クエリをポイントクエリに変換して最適化する方法について説明します。

ポイントクエリは、アプリケーションがオンライントランザクション処理 (OLTP) データベースにアクセスするときに一般的に使用されます。 ポイントクエリでは、テーブル内の少量のデータのみがスキャンされて結果が返されます。 たとえば、Taobaoショッピングプラットフォームで注文や商品情報を表示する場合、データベースで実行される操作はポイントクエリです。 PolarDB-Xは応答時間を短縮し、ポイントクエリのリソース使用量を改善します。 PolarDB-Xは高スループットをサポートし、多数の読み取り要求が同時に処理されるシナリオに適しています。

ポイントクエリとは何ですか?

ポイントクエリは、少量のデータのみがスキャンされるクエリである。 少量のデータが返されるが、大量のデータがクエリでスキャンされる場合、クエリはポイントクエリではありません。 たとえば、select * from t1 order by c1 limit 1は1つのデータレコードのみを返しますが、このクエリはポイントクエリではない場合があります。 その理由は、c1にインデックスが作成されない場合、t1のすべてのデータをスキャンしてソートして結果を返す必要があるためです。

スタンドアロンデータベースでは、最も一般的なポイントクエリは、プライマリキーでデータをクエリすることです。 主キーインデックスをスキャンした後、結果をすばやく返すことができます。 ほとんどの場合、lognレコードのみをスキャンする必要があります。 他の条件を使用してデータをクエリする場合は、ローカルのセカンダリインデックスを作成できます。 このようにして、ローカルの2次インデックスをスキャンして1次キーを取得します。 次に、インデックス行IDに基づいてテーブルにアクセスし、完全なレコードを取得します。 ローカルのセカンダリインデックスにクエリに含まれるすべての列が含まれるシナリオでは、インデックス行IDに基づいてテーブルにアクセスする必要はありません。

PolarDB-Xは分散データベースサービスです。 パーティションテーブルは、データを異なるデータノード間で分散できるようにサポートされています。 データは複数のパーティションに分割され、パーティションとデータノード間のマッピングが確立されます。 データ分割のディメンションとして1つ以上の列が選択されます。 これらの列はパーティションキーを構成します。 分散データベースでは、クエリのパフォーマンスはスキャンされたデータの量に線形に関連し、スキャンされたパーティションの数に正の相関があります。 したがって、ポイントクエリの定義には、次の情報を含める必要があります。少数のパーティションがスキャンされます。

PolarDB-Xは、透明性と分散機能を提供します。 デフォルトでは、主キーがパーティションキーとして使用されます。 主キーに基づいてデータが照会されると、データを格納するパーティションが最初に配置され、次にパーティションの主キーインデックスがスキャンされて結果が返されます。 この方法は最高のパフォーマンスを提供します。 他の条件を使用してデータをクエリする場合は、グローバルセカンダリインデックスを作成できます。 詳細については、「GSI」をご参照ください。 グローバルセカンダリインデックスは、ローカルセカンダリインデックスと同じ方法でクエリを最適化するために使用できます。 主キーが取得され、インデックス行IDに基づいてテーブルにアクセスして、完全なレコードを取得します。 違いは、グローバルセカンダリインデックスもパーティションテーブルであることです。 データとベーステーブルは異なるデータノードに格納されます。 ほとんどの場合、データはノード間で転送され、インデックス行IDに基づいてテーブルにアクセスするときにネットワークオーバーヘッドが必要になります。 インデックス行IDに基づいてテーブルにアクセスするコストは、スタンドアロンデータベースのデータをスキャンするコストよりも高くなります。 PolarDB-Xでは、クラスター化インデックスを作成して、インデックス行IDに基づいてテーブルにアクセスする必要をなくすことができます。 これにより、クエリのパフォーマンスが主キーに基づいて実行されるクエリと同じになります。

ノート

ローカルセカンダリインデックスとグローバルセカンダリインデックスは、追加のストレージと書き込みオーバーヘッドを犠牲にして高いクエリ性能を提供するために使用されます。 ローカルセカンダリインデックスとグローバルセカンダリインデックスを使用する前に、書き込みパフォーマンスへの影響を評価することをお勧めします。 データは、インデックステーブルとベーステーブルとの間で異なる方法で分配される。 グローバルセカンダリインデックスに関連するすべての書き込み操作は、デフォルトで分散トランザクションにカプセル化されます。 これにより、グローバルセカンダリインデックスデータとベーステーブル内のデータとの間の強い一貫性が保証されます。 グローバルセカンダリインデックスが作成されないシナリオと比較して、書き込み要求の応答時間は2〜3倍に増加します。 データは、インデックステーブルおよびベーステーブルに並列に書き込まれる。 結果として、高度に同時の書き込みシナリオにおいて分散デッドロックを生成する確率が増加する。 各論理テーブルに最大3つのグローバルセカンダリインデックスを作成することを推奨します。

ポイントクエリの特定

分散データベースのポイントクエリは、少数のシャードとデータがスキャンされるクエリです。 ステートメントの実行計画を表示することで、クエリステートメントに対してスキャンされたシャードの数を確認できます。 実行計画の詳細については、「オペレーター」をご参照ください。 次のコードブロックは、ポイントクエリの例を示しています。

> 説明c_custkey = 42の顧客からc_custkey、c_name、c_addressを選択します。+ ------------------------------------------------------------------------------------------------------------------------------------------------------------------ +
| ローカル実行 |
+ ------------------------------------------------------------------------------------------------------------------------------------------------------------------ +
| LogicalView(tables="TEST1_000002_GROUP.customer_IVgG_10", sql="SELECT 'c_custkey', 'c_name', 'c_address' FROM 'customer' AS 'customer' WHERE ('c_custkey' = ?)") |
+ ------------------------------------------------------------------------------------------------------------------------------------------------------------------ + 

EXPLAIN EXECUTEは、データノードの実行計画を要約して表示するために使用されます。 これにより、クエリがデータノードの正しいインデックスにヒットするかどうかを判断できます。 データノードの実行計画は、MySQLに基づいて実装されます。 実行プランは、MySQLの実行プランと同じ方法で処理されます。 詳細については、「MySQL公式ドキュメント」をご参照ください。 次のコードブロックは、ポイントクエリの例を示しています。

> execute select c_custkey, c_name, c_address from customer where c_custkey = 42;
---- ----------- -----------------------------------------------------------------------------------
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
---- ----------- -----------------------------------------------------------------------------------
| 1 | SIMPLE | customer | NULL | const | PRIMARY | 4 | const | 1 | 100 | pkアクセスの使用 |
---- ----------- --------------------------------------------------------------------------------------......... + + +- 

最適化のためにクエリをポイントクエリに変換する

すべてのクエリを最適化のためにポイントクエリに変換することはできません。 たとえば、次のクエリはポイントクエリに変換できません。データを取得するためのフィルタリング条件を含まないselect * from t1クエリ、不適切なページクエリselect * from t1 where c1 = 1 limit 100000、10、およびビジネスの成長によりパラメーターの数が増加するINクエリです。 次の2種類のクエリをポイントクエリに変換して最適化できます。
  1. スキャン範囲が固定されているクエリ (同等の条件を含む、または同等の条件として簡略化できる条件、スキャン範囲を絞り込むBETWEEN AND条件、パラメーター数が固定されているin条件など)
  2. select * from t1 where c1 > 42 limit 10 and select * from t1 order by c1 limit 10select * from t1 order by c1 limit 10など、固定数の行が返されるTopNクエリ

適切なインデックスを作成してフルテーブルスキャンをインデックススキャンに変換することで、これら2種類のクエリを最適化できます。 次のコードブロックに例を示します。

CREATE TABLE 'customer' (
  'c_custkey' int(11) NOT NULL、
  'c_name' varchar(25) NOT NULL、
  'c_address' varchar(40) NOT NULL、
  'c_nationkey' int(11) NOT NULL、
  'c_phone' varchar (15) NOT NULL、
  'c_acctbal' decimal(15,2) NOT NULL、
  'c_mktsegment' varchar(10) NOT NULL、
  'c_comment' varchar(117) NOT NULL、
  主要なキー ('c_custkey')
) ENGINE=InnoDB DEFAULT CHARSET=latin1 dbpartition by hash('c_custkey ') tbpartition by hash('c_custkey') tbpartition 4;

> 説明select * from customer where c_phone = "11";
+ -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +
| ローカル実行 |
+ -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +
| 収集 (同時=true) |
| LogicalView(tables="[000000-000003].customer_[00-15]", shardCount=16, sql="SELECT 'c_custkey ', 'c_name', 'c_address', 'c_nationkey', 'c_phone ', 'c_acctbal', ''c_mktsegment', ''customer'='
+ -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + 

顧客テーブルには主キーインデックスのみがあります。 c_phoneには同等の条件が指定されていますが、すべてのシャードをスキャンする必要があります。 グローバルセカンダリインデックスを作成することで、このクエリを最適化できます。

> 顧客 (c_phone) にグローバルインデックスg_i_phoneを作成するdbpartition by hash(c_phone);

> 説明select * from customer where c_phone = "11";
+ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +
| ローカル実行 |
+ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +
| プロジェクト (c_custkey="c_custkey" 、c_name="c_name" 、c_address="c_address" 、c_nationkey="c_nationkey" 、c_phone="c_phone" 、c_acctbal="c_acctbal" 、c_mktsegment="、c_comment |)
| BKAJoin(condition="c_custkey = c_custkey", type="inner") |
| IndexScan(tables="TEST1_000000_GROUP.g_i_phone_2CSp", sql="SELECT 'c_custkey', 'c_phone' FROM 'g_i_phone 'AS 'g_i_phone' WHERE ('c_phone' = ?)") |
| 収集 (同時=true) |
| LogicalView(tables="[000000-000003].customer_[00-15]", shardCount=16, sql="SELECT 'c_custkey ', 'c_name', 'c_address', 'c_nationkey', 'c_acctbal', 'c_mktsegment', 'c_comment' FROM 'customer'='AND ('c_custkey' IN (...)))" |
+ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + 

グローバルセカンダリインデックスが作成された後、クエリは2つのフェーズで実行できます。インデックステーブルに対するポイントクエリと、インデックス行IDに基づくテーブルアクセスです。 2番目のフェーズでは、1つのシャードのみがアクセスされます。 実行計画では、インデックス行IDに基づいてテーブルにアクセスすると、ベーステーブルに対するテーブル全体のスキャンが実行されます。 これは、スキャンする必要があるベーステーブルのシャードがインデックステーブルのクエリの結果に依存し、説明フェーズで決定できないためです。

> ドロップインデックスg_i_phone on customer;

> ハッシュ (c_phone) による顧客 (c_phone) dbpartitionのクラスタ化インデックスg_i_phoneを作成します。> 説明select * from customer where c_phone = "11";
+ ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +
| ローカル実行 |
+ ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +
| IndexScan(tables="TEST1_000000_GROUP.g_i_phone_fHmZ", sql="SELECT 'c_custkey', 'c_name', 'c_address', 'c_nationkey', 'c_phone', 'c_acctbal', 'c_mktsegment', 'c_comment' FROM 'g_i_phone '' '' ''phone' |
+ ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + 

グローバルセカンダリインデックスの代わりにクラスター化インデックスを使用すると、インデックステーブルにベーステーブルのすべての列が含まれるため、インデックス行IDに基づいてテーブルにアクセスする必要はありません。 そして、実行プランでは、インデックステーブル上のポイントクエリのみが実行されます。

上記の例では、インデックスを使用してポイントクエリのパフォーマンスを向上させる一般的な手順について説明します。 重要なポイントは、クエリの特性に基づいてインデックスが作成される適切な列を見つけることです。 複数の条件を含む複雑なクエリの場合、PolarDB-Xの組み込みインデックス推奨機能を使用して、適切なローカルセカンダリインデックスとグローバルセカンダリインデックスを見つけることができます。 詳細については、「インテリジェントインデックスの推奨」をご参照ください。