TiDB を使用したJavaアプリケーション開発のベスト プラクティス
このドキュメントでは、TiDBをより有効に活用するためのJavaアプリケーション開発のベストプラクティスを紹介します。バックエンドのTiDBデータベースとやり取りする一般的なJavaアプリケーションコンポーネントに基づいて、開発中によく発生する問題への解決策も提供します。
Javaアプリケーションのデータベース関連コンポーネント
Javaアプリケーションで TiDB データベースと対話する一般的なコンポーネントは次のとおりです。
- ネットワーク プロトコル: クライアントは標準MySQLプロトコルを介して TiDBサーバーと対話します。
- JDBC APIとJDBCドライバ: Javaアプリケーションは通常、標準のJDBC (Javaデータベース接続) APIを使用してデータベースにアクセスします。TiDBに接続するには、JDBC APIを介してMySQLプロトコルを実装したJDBCドライバを使用できます。MySQLで一般的なJDBCドライバには、 MySQL コネクタ/JとMariaDB コネクタ/Jあります。
- データベース接続プール:接続が要求されるたびに作成するオーバーヘッドを削減するため、アプリケーションは通常、接続プールを使用して接続をキャッシュし、再利用します。JDBC データソース接続プールAPIが定義されています。必要に応じて、様々なオープンソースの接続プール実装から選択できます。
- データ アクセス フレームワーク: アプリケーションは通常、 マイバティスや休止状態などのデータ アクセス フレームワークを使用して、データベース アクセス操作をさらに簡素化および管理します。
- アプリケーション実装:アプリケーションロジックは、データベースにどのコマンドをいつ送信するかを制御します。一部のアプリケーションでは、トランザクションの開始とコミットのロジックを管理するために春のトランザクションアスペクトを使用します。

上の図から、 Javaアプリケーションが次のことを実行する可能性があることがわかります。
- TiDB と対話するために、JDBC API を介して MySQL プロトコルを実装します。
- 接続プールから永続的な接続を取得します。
- MyBatis などのデータ アクセス フレームワークを使用して、SQL ステートメントを生成および実行します。
- Spring トランザクションを使用して、トランザクションを自動的に開始または停止します。
このドキュメントの残りの部分では、上記のコンポーネントを使用してJavaアプリケーションを開発する際に発生する問題とその解決策について説明します。
JDBC
Javaアプリケーションは様々なフレームワークでカプセル化されたできます。ほとんどのフレームワークでは、データベースサーバーとやり取りするために最下層でJDBC APIが呼び出されます。JDBCでは、以下の点に重点を置くことをお勧めします。
- JDBC APIの使用選択
- API実装者のパラメータ設定
JDBC API
JDBC APIの使用方法については、 JDBC公式チュートリアル参照してください。このセクションでは、いくつかの重要なAPIの使用方法について説明します。
準備APIを使用する
OLTP(オンライントランザクション処理)シナリオでは、プログラムからデータベースに送信されるSQL文は複数の種類に分かれており、パラメータの変更を取り除いた後には使い尽くされてしまう可能性があります。そのため、通常のテキストファイルからの実行ではなく準備された声明使用し、Prepared Statementを再利用して直接実行することをお勧めします。これにより、TiDBでSQL実行プランを繰り返し解析および生成するオーバーヘッドを回避できます。
現在、上位フレームワークのほとんどはSQL実行時にPrepare APIを呼び出します。開発でJDBC APIを直接使用する場合は、Prepare APIを選択する際に注意してください。
また、MySQL Connector/J のデフォルト実装では、クライアント側のステートメントのみが前処理され、クライアント側で?が置換された後、テキストファイルでサーバーに送信されます。そのため、TiDBサーバーでステートメントの前処理を行う前に、Prepare API の使用に加えて、JDBC 接続パラメータのuseServerPrepStmts = trueも設定する必要があります。詳細なパラメータ設定については、 MySQL JDBCパラメータ参照してください。
バッチAPIを使用する
バッチ挿入の場合は、 addBatch / executeBatch API使用できます。3 addBatch()方法は、複数のSQL文をまずクライアント側でキャッシュし、 executeBatch方法を呼び出したときにそれらをまとめてデータベースサーバーに送信するために使用されます。
注記:
MySQL Connector/Jのデフォルトの実装では、
addBatch()でバッチに追加されたSQL文の送信はexecuteBatch()が呼び出された時点まで遅延されますが、実際のネットワーク転送では文は1つずつ送信されます。そのため、この方法では通常、通信オーバーヘッドは削減されません。バッチネットワーク転送を行う場合は、JDBC接続パラメータの
rewriteBatchedStatements = true設定する必要があります。詳細なパラメータ設定については、 バッチ関連のパラメータ参照してください。
StreamingResultを使用して実行結果を取得します
多くの場合、JDBCは実行効率を向上させるために、クエリ結果を事前に取得し、デフォルトでクライアントのメモリに保存します。しかし、クエリが返す結果セットが非常に大きい場合、クライアントはデータベースサーバーに一度に返されるレコード数を減らすよう要求し、クライアントのメモリが準備できて次のバッチを要求するまで待機することがよくあります。
JDBC では通常、次の 2 つの処理方法が使用されます。
最初の方法:
FetchSizeInteger.MIN_VALUEに設定するクライアントがキャッシュしないようにします。クライアントはStreamingResultを介してネットワーク接続から実行結果を読み取ります。クライアントがストリーミング読み取り方式を使用する場合、クエリを実行するためにステートメントを続行する前に、読み取りを完了するか、
resultset閉じる必要があります。そうでない場合は、エラーNo statements may be issued when any streaming result sets are open and in use on a given connection. Ensure that you have called .close() on any active streaming result sets before attempting more queries.が返されます。クライアントが読み取りを完了するか
resultset閉じる前にクエリでこのようなエラーが発生するのを回避するには、URL にclobberStreamingResults=trueパラメータを追加します。これにより、resultset自動的に閉じられますが、前のストリーミングクエリで読み取られるべき結果セットは失われます。2 番目の方法: 最初に
FetchSizeの設定正の整数として設定し、次に JDBC URL でuseCursorFetch = true設定してカーソル フェッチを使用します。
TiDB は両方の方法をサポートしていますが、実装がより単純で実行効率が優れているため、 FetchSizeをInteger.MIN_VALUEに設定する最初の方法を使用することをお勧めします。
2番目の方法では、TiDBはまずすべてのデータをTiDBノードにロードし、その後FetchSizeに従ってクライアントにデータを返します。そのため、通常は1番目の方法よりも多くのメモリを消費します。3 ON tidb_enable_tmp_storage_on_oom設定した場合、TiDBは結果を一時的にハードディスクに書き込む可能性があります。
tidb_enable_lazy_cursor_fetchシステム変数をONに設定すると、TiDB はクライアントがデータを取得する際にのみデータの一部を読み取ろうとするため、メモリ使用量が少なくなります。詳細と制限事項については、 tidb_enable_lazy_cursor_fetchシステム変数の完全な説明ご覧ください。
MySQL JDBCパラメータ
JDBCは通常、実装関連の設定をJDBC URLパラメータの形で提供します。このセクションではMySQL Connector/Jのパラメータ設定説明します(MariaDBをご利用の場合はMariaDBのパラメータ設定参照してください)。このドキュメントではすべての設定項目を網羅することはできないため、主にパフォーマンスに影響を与える可能性のあるいくつかのパラメータに焦点を当てます。
準備関連のパラメータ
このセクションでは、 Prepareに関連するパラメータを紹介します。
useServerPrepStmts
デフォルトではuseServerPrepStmtsはfalseに設定されています。つまり、Prepare API を使用した場合でも、「準備」操作はクライアント側でのみ実行されます。サーバーの解析オーバーヘッドを回避するため、同じ SQL 文で Prepare API を複数回使用する場合は、この設定をtrueに設定することをお勧めします。
この設定がすでに有効になっていることを確認するには、次の操作を実行します。
- TiDB 監視ダッシュボードに移動し、クエリ サマリー>インスタンス別の CPSを通じて要求コマンド タイプを表示します。
- リクエスト内の
COM_QUERYCOM_STMT_EXECUTEまたはCOM_STMT_PREPAREに置き換えられた場合、この設定は既に有効になっていることを意味します。
cachePrepStmts
useServerPrepStmts=trueではサーバーがプリペアドステートメントを実行できますが、デフォルトではクライアントはプリペアドステートメントを毎回実行した後に閉じ、再利用しません。つまり、「準備」操作はテキストファイルの実行ほど効率的ではありません。この問題を解決するには、 useServerPrepStmts=true設定した後、 cachePrepStmts=trueも設定することをお勧めします。これにより、クライアントはプリペアドステートメントをキャッシュできるようになります。
この設定がすでに有効になっていることを確認するには、次の操作を実行します。
- TiDB 監視ダッシュボードに移動し、クエリ サマリー>インスタンス別の CPSを通じて要求コマンド タイプを表示します。
- リクエスト内の
COM_STMT_EXECUTEの数がCOM_STMT_PREPARE数よりはるかに多い場合、この設定はすでに有効になっていることを意味します。
また、 useConfigs=maxPerformance設定すると、 cachePrepStmts=true含む複数のパラメータが同時に設定されます。
prepStmtCacheSqlLimit
cachePrepStmts設定した後は、 prepStmtCacheSqlLimit設定にも注意してください(デフォルト値は256です)。この設定は、クライアントにキャッシュされるプリペアドステートメントの最大長を制御します。
この最大長を超えるプリペアドステートメントはキャッシュされないため、再利用できません。この場合、アプリケーションの実際のSQLの長さに応じて、この設定の値を増やすことを検討してください。
次の場合は、この設定が小さすぎないかどうかを確認する必要があります。
- TiDB 監視ダッシュボードに移動し、クエリ サマリー>インスタンス別の CPSを通じて要求コマンド タイプを表示します。
- そして、
cachePrepStmts=true設定されていることがわかりますが、COM_STMT_PREPAREまだCOM_STMT_EXECUTEとほぼ同じで、COM_STMT_CLOSE存在します。
prepStmtCacheSize
prepStmtCacheSizeキャッシュされるプリペアドステートメントの数を制御します(デフォルト値は25です)。アプリケーションで多くの種類のSQL文を「準備」する必要があり、プリペアドステートメントを再利用したい場合は、この値を増やすことができます。
この設定がすでに有効になっていることを確認するには、次の操作を実行します。
- TiDB 監視ダッシュボードに移動し、クエリ サマリー>インスタンス別の CPSを通じて要求コマンド タイプを表示します。
- リクエスト内の
COM_STMT_EXECUTEの数がCOM_STMT_PREPARE数よりはるかに多い場合、この設定はすでに有効になっていることを意味します。
バッチ関連のパラメータ
バッチ書き込み処理中は、 rewriteBatchedStatements=true設定することをお勧めします。 addBatch()またはexecuteBatch()使用した後でも、JDBCはデフォルトでSQLを1つずつ送信します。例:
pstmt = prepare("insert into t (a) values(?)");
pstmt.setInt(1, 10);
pstmt.addBatch();
pstmt.setInt(1, 11);
pstmt.addBatch();
pstmt.setInt(1, 12);
pstmt.executeBatch();
Batch方法が使用されていますが、TiDB に送信される SQL ステートメントは個別のINSERTステートメントのままです。
insert into t(a) values(10);
insert into t(a) values(11);
insert into t(a) values(12);
ただし、 rewriteBatchedStatements=true設定すると、TiDB に送信される SQL ステートメントは単一のINSERTステートメントになります。
insert into t(a) values(10),(11),(12);
INSERT番目の文の書き換えは、複数の「values」キーワードの後の値を連結して、1 つの SQL 文にまとめるというものです。3 INSERT文に他の違いがある場合は、書き換えることはできません。例えば、次のようになります。
insert into t (a) values (10) on duplicate key update a = 10;
insert into t (a) values (11) on duplicate key update a = 11;
insert into t (a) values (12) on duplicate key update a = 12;
上記のINSERT文を1つの文に書き直すことはできません。しかし、3つの文を次のように書き換えると、
insert into t (a) values (10) on duplicate key update a = values(a);
insert into t (a) values (11) on duplicate key update a = values(a);
insert into t (a) values (12) on duplicate key update a = values(a);
すると、書き換え要件を満たします。上記のINSERTは、次の1つの文に書き換えられます。
insert into t (a) values (10), (11), (12) on duplicate key update a = values(a);
バッチ更新中に3つ以上の更新が発生した場合、SQL文は書き換えられ、複数のクエリとして送信されます。これにより、クライアントからサーバーへのリクエストのオーバーヘッドは効果的に削減されますが、副作用として生成されるSQL文のサイズが大きくなります。例えば、次のようになります。
update t set a = 10 where id = 1; update t set a = 11 where id = 2; update t set a = 12 where id = 3;
また、 クライアントのバグため、バッチ更新中にrewriteBatchedStatements=trueとuseServerPrepStmts=true設定する場合は、このバグを回避するためにallowMultiQueries=trueパラメータも設定することをお勧めします。
パラメータを統合する
監視中に、アプリケーションがTiDBクラスタに対してINSERT操作しか実行していないにもかかわらず、冗長なSELECTステートメントが多数存在することに気付く場合があります。これは通常、JDBCが設定を照会するためにいくつかのSQLステートメント(例えばselect @@session.transaction_read_only )を送信するために発生します。これらのSQLステートメントはTiDBには役に立たないため、余分なオーバーヘッドを回避するためにuseConfigs=maxPerformance設定することをお勧めします。
useConfigs=maxPerformanceには一連の設定が含まれています。MySQL Connector/J 8.0とMySQL Connector/J 5.1の詳細な設定については、それぞれmysql-コネクタ-j 8.0とmysql-コネクタ-j 5.1参照してください。
設定後、監視をチェックして、 SELECTステートメントの数が減っていることを確認できます。
注記:
useConfigs=maxPerformance有効にするには、MySQL Connector/J バージョン 8.0.33 以降が必要です。詳細についてはMySQL JDBC バグ参照してください。
タイムアウト関連のパラメータ
TiDB は、次の MySQL 互換のタイムアウト制御パラメータを提供します。
wait_timeout: Javaアプリケーションへの接続における非対話型アイドルタイムアウトを制御します。TiDB v5.4以降では、デフォルト値はwait_timeoutで、これは28800秒(8時間)です。TiDB v5.4より前のバージョンでは、デフォルト値は0で、これはタイムアウトが無制限であることを意味します。interactive_timeout: Javaアプリケーションへの接続における対話型アイドルタイムアウトを制御します。デフォルト値は8時間です。max_execution_time: 接続におけるSQL実行のタイムアウトを制御します。読み取り専用SQL文にのみ有効です。デフォルト値は0で、接続が無限にビジー状態になることを許可します。つまり、SQL文は無限に長い時間実行されます。
しかし、実際の本番環境では、アイドル接続や実行時間が長すぎるSQL文は、データベースやアプリケーションに悪影響を及ぼします。アイドル接続や実行時間が長すぎるSQL文を回避するには、アプリケーションの接続文字列でこれらの2つのパラメータを設定できます。例えば、 sessionVariables=wait_timeout=3600 (1時間)とsessionVariables=max_execution_time=300000 (5分)を設定します。
一般的なJDBC接続文字列パラメータ
上記のパラメータ値を組み合わせると、JDBC 接続文字列の構成は次のようになります。
jdbc:mysql://<IP_ADDRESS>:<PORT_NUMBER>/<DATABASE_NAME>?characterEncoding=UTF-8&useSSL=false&useServerPrepStmts=true&cachePrepStmts=true&prepStmtCacheSqlLimit=10000&prepStmtCacheSize=1000&useConfigs=maxPerformance&rewriteBatchedStatements=true
注記:
パブリックネットワーク経由で接続する場合は、
useSSL=trueとTiDBクライアントとサーバー間のTLSを有効にする設定する必要があります。
接続プール
TiDB (MySQL) 接続の構築は、TCP 接続の構築に加えて接続認証も必要となるため、比較的コストがかかります(少なくとも OLTP シナリオでは)。そのため、クライアントは通常、TiDB (MySQL) 接続を接続プールに保存し、再利用できるようにします。
TiDB は次のJava接続プールをサポートしています。
実際には、一部の接続プールは特定のアクティブセッションを永続的に使用する場合があります。接続の総数はTiDBコンピューティングノード間で均等に分散されているように見えますが、アクティブ接続の不均一な分散は、実際の負荷の不均衡につながる可能性があります。分散シナリオでは、接続ライフサイクルを効果的に管理し、アクティブ接続が特定のノードに固定されるのを防ぎ、バランスの取れた負荷分散を実現するHikariCPの使用をお勧めします。
一般的な接続プールの構成
以下は、 HikariCPの設定例です。
hikari:
maximumPoolSize: 20
poolName: hikariCP
connectionTimeout: 30000
maxLifetime: 1200000
keepaliveTime: 120000
パラメータの説明は以下のとおりです。詳細についてはHikariCP公式ドキュメントを参照してください。
maximumPoolSize: プール内の最大接続数。デフォルト値は10です。コンテナ環境では、 Javaアプリケーションで利用可能なCPUコア数の4~10倍に設定することをお勧めします。この値を高く設定しすぎるとリソースの無駄遣いにつながる可能性があり、低く設定しすぎると接続の取得が遅くなる可能性があります。詳細はプールのサイズについて参照してください。minimumIdle: HikariCP、このパラメータの設定を推奨しません。デフォルト値はmaximumPoolSizeで、接続プールのスケーリングは無効になります。これにより、トラフィックの急増時でも接続が常に利用可能になり、接続作成による遅延を回避できます。connectionTimeout: アプリケーションがプールから接続を取得するまでの最大待機時間(ミリ秒単位)。デフォルト値は30000ミリ秒(30秒)です。この時間内に利用可能な接続を取得できない場合は、例外SQLExceptionが発生します。maxLifetime: プール内の接続の最大存続時間(ミリ秒単位)。デフォルト値は1800000ミリ秒(30 分)です。使用中の接続は影響を受けません。接続が閉じられた後、この設定に従って削除されます。この値が低すぎると、再接続が頻繁に発生する可能性があります。4graceful-wait-before-shutdown使用する場合は、この値が待機時間よりも小さいことを確認してください。keepaliveTime: プール内の接続に対するキープアライブ操作の間隔(ミリ秒単位)。この設定は、データベースまたはネットワークのアイドルタイムアウトによる切断を防ぐのに役立ちます。デフォルト値は120000ミリ秒(2分)です。プールは、アイドル接続を維持するためにJDBC4isValid()方式を優先的に使用します。
プローブ構成
接続プールは、次のようにクライアントから TiDB への永続的な接続を維持します。
- v5.4 より前では、TiDB はデフォルトでクライアント接続を積極的に閉じません (エラーが報告されない限り)。
- バージョン5.4以降、TiDBはデフォルトで
28800秒(つまり8時間)の非アクティブ状態が続くとクライアント接続を自動的に閉じます。このタイムアウト設定は、TiDBとMySQL互換のwait_timeout変数を使用して制御できます。詳細については、 JDBCクエリタイムアウト参照してください。
さらに、クライアントとTiDBの間には、 LVSやHAプロキシようなネットワークプロキシが存在する場合があります。これらのプロキシは通常、一定のアイドル時間(プロキシのアイドル設定によって決定されます)が経過すると、接続をプロアクティブにクリーンアップします。プロキシのアイドル設定を監視するだけでなく、接続プールはキープアライブのために接続を維持またはプローブする必要があります。
Javaアプリケーションで次のエラーが頻繁に表示される場合:
The last packet sent successfully to the server was 3600000 milliseconds ago. The driver has not received any packets from the server. com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure
n milliseconds ago分のn 0または非常に小さい値である場合、通常は実行されたSQL操作によってTiDBが異常終了したことが原因です。原因を特定するには、TiDBのstderrログを確認することをお勧めします。
n非常に大きな値(上記の例では3600000など)の場合、この接続は長時間アイドル状態のままで、その後中間プロキシによって切断された可能性があります。通常の解決策は、プロキシのアイドル設定の値を増やし、接続プールで以下の処理を実行することです。
- 毎回接続を使用する前に接続が利用可能かどうかを確認してください
- 別のスレッドを使用して、接続が利用可能かどうかを定期的に確認します。
- 接続を維持するために定期的にテストクエリを送信する
接続プールの実装によっては、上記の方法のうち1つ以上をサポートしている場合があります。対応する設定については、接続プールのドキュメントをご確認ください。
データアクセスフレームワーク
アプリケーションでは、データベース アクセスを簡素化するために、何らかのデータ アクセス フレームワークを使用することが多いです。
マイバティス
マイバティス 、広く普及しているJavaデータアクセスフレームワークです。主にSQLクエリの管理と、結果セットとJavaオブジェクトのマッピングに使用されます。MyBatisはTiDBと高い互換性があります。MyBatisはこれまでの歴史から、問題が発生することはほとんどありません。
このドキュメントでは、主に以下の構成に焦点を当てています。
マッパーパラメータ
MyBatis Mapper は次の 2 つのパラメータをサポートしています。
select 1 from t where id = #{param1}Prepared Statement としてselect 1 from t where id =?に変換され、「準備済み」となり、実際のパラメータは再利用のために使用されます。このパラメータを前述の Prepare 接続パラメータと併用すると、最高のパフォーマンスが得られます。select 1 from t where id = ${param2}テキストファイルとしてselect 1 from t where id = 1に置き換えられ、実行されます。このステートメントが異なるパラメータに置き換えられて実行されると、MyBatis は TiDB にステートメントを「準備」するための異なるリクエストを送信します。これにより、TiDB は多数の Prepared Statement をキャッシュする可能性があり、この方法で SQL 操作を実行するとインジェクションのセキュリティリスクが生じます。
動的SQLバッチ
複数のINSERT文をinsert ... values(...), (...), ...形式に自動書き換えるために、前述のようにJDBCでrewriteBatchedStatements=true設定することに加え、MyBatisでは動的SQLを使用して半自動でバッチ挿入を生成することもできます。以下のマッパーを例に挙げましょう。
<insert id="insertTestBatch" parameterType="java.util.List" fetchSize="1">
insert into test
(id, v1, v2)
values
<foreach item="item" index="index" collection="list" separator=",">
(
#{item.id}, #{item.v1}, #{item.v2}
)
</foreach>
on duplicate key update v2 = v1 + values(v1)
</insert>
このマッパーはinsert on duplicate key update文を生成します。その後に続く(?,?,?)の「値」の数は、渡されたリストの数によって決まります。最終的な効果はrewriteBatchStatements=true使用した場合と似ており、クライアントとTiDB間の通信オーバーヘッドも効果的に削減されます。
前述のように、準備済みステートメントの最大長がprepStmtCacheSqlLimitを超えるとキャッシュされなくなることにも注意する必要があります。
ストリーミング結果
前のセクション 、JDBCで実行結果をストリーム読み取りする方法が導入されました。JDBCの対応する設定に加えて、MyBatisで非常に大きな結果セットを読み取る場合は、以下の点にも注意する必要があります。
- マッパー設定で単一のSQL文に
fetchSize設定できます(前のコードブロックを参照)。これはJDBCでsetFetchSize呼び出すのと同じ効果があります。 - クエリ インターフェイスを
ResultHandlerで使用すると、一度に結果セット全体を取得することを回避できます。 - ストリーム読み取りには
Cursorクラスを使用できます。
XML を使用してマッピングを構成する場合は、マッピングの<select>セクションでfetchSize="-2147483648" ( Integer.MIN_VALUE ) を構成することで読み取り結果をストリーミングできます。
<select id="getAll" resultMap="postResultMap" fetchSize="-2147483648">
select * from post;
</select>
コードを使用してマッピングを構成する場合は、 @Options(fetchSize = Integer.MIN_VALUE)アノテーションを追加し、結果のタイプをCursorのままにして、SQL 結果をストリーミングで読み取ることができるようにすることができます。
@Select("select * from post")
@Options(fetchSize = Integer.MIN_VALUE)
Cursor<Post> queryAllPost();
ExecutorType
openSession間にExecutorType選択できます。MyBatis は 3 種類のエグゼキューターをサポートしています。
- シンプル: 準備されたステートメントは実行ごとにJDBCに呼び出されます(JDBC構成項目
cachePrepStmts有効になっている場合は、繰り返し準備されたステートメントが再利用されます) - 再利用: 準備済みステートメントは
executorにキャッシュされるため、JDBCcachePrepStmtsを使用せずに準備済みステートメントの重複呼び出しを削減できます。 - バッチ:各更新操作(
INSERT/DELETE/UPDATE)はまずバッチに追加され、トランザクションがコミットされるか、SELECTクエリが実行されるまで実行されます。JDBCレイヤーでrewriteBatchStatements有効になっている場合は、ステートメントの書き換えが試行されます。有効になっていない場合は、ステートメントが1つずつ送信されます。
通常、デフォルト値ExecutorTypeはSimpleです。7 openSession呼び出す場合はExecutorType変更する必要があります。バッチ実行の場合、トランザクション内でUPDATEまたはINSERTステートメントは非常に高速に実行されるものの、データの読み取りやトランザクションのコミット時に速度が低下することがあります。これは実際には正常な動作であるため、遅いSQLクエリのトラブルシューティングを行う際にはこの点に注意する必要があります。
春のトランザクション
現実の世界では、アプリケーションは春のトランザクションと AOP の側面を使用してトランザクションを開始および停止する場合があります。
メソッド定義に@Transactionalアノテーションを追加すると、AOPはメソッドが呼び出される前にトランザクションを開始し、メソッドが結果を返す前にトランザクションをコミットします。アプリケーションで同様のニーズがある場合は、コード内の@Transactional使用して、トランザクションの開始と終了のタイミングを判断できます。
埋め込みの特殊なケースに注意してください。このケースが発生した場合、Springは伝搬設定に基づいて異なる動作をします。
その他
このセクションでは、問題のトラブルシューティングに役立つJavaの便利なツールをいくつか紹介します。
トラブルシューティングツール
Javaアプリケーションで問題が発生し、アプリケーションロジックが不明な場合は、JVMの強力なトラブルシューティングツールの使用をお勧めします。以下に、一般的なツールをいくつか紹介します。
jスタック
jスタックは Go の pprof/goroutine に似ており、プロセスがスタックする問題を簡単にトラブルシューティングできます。
jstack pid実行すると、対象プロセス内のすべてのスレッドのIDとスタック情報を出力できます。デフォルトではJavaスタックのみが出力されます。JVM内のC++スタックも同時に出力したい場合は、 -mオプションを追加してください。
jstack を複数回使用することで、スタックの問題 (たとえば、Mybatis で Batch ExecutorType を使用しているためにアプリケーションのビューからのクエリが遅いなど) やアプリケーションのデッドロックの問題 (たとえば、アプリケーションが SQL ステートメントを送信する前にロックをプリエンプトしているために SQL ステートメントを送信しないなど) を簡単に見つけることができます。
さらに、 top -p $ PID -HまたはJava のスイスナイフは、スレッドIDを確認する一般的な方法です。また、「スレッドが大量のCPUリソースを占有し、何を実行しているのかわからない」という問題を特定するには、次の手順を実行してください。
- スレッド ID を 16 進数に変換するには
printf "%x\n" pid使用します。 - 対応するスレッドのスタック情報を見つけるには、jstack 出力に移動します。
jmapとmat
Go の pprof/heap とは異なり、 jmapプロセス全体のメモリスナップショットをダンプし (Go ではディストリビューターのサンプリング)、そのスナップショットを別のツールマットで分析できます。
mat を使用すると、プロセス内のすべてのオブジェクトの関連情報と属性を確認できるほか、スレッドの実行状態を観察することもできます。例えば、mat を使用すると、現在のアプリケーションに存在する MySQL 接続オブジェクトの数や、各接続オブジェクトのアドレスとステータス情報を確認できます。
mat はデフォルトでは到達可能なオブジェクトのみを処理することに注意してください。若いGCの問題をトラブルシューティングしたい場合は、mat の設定を調整して到達不可能なオブジェクトを表示できます。また、若いGCの問題(または多数の短命オブジェクト)のメモリ割り当てを調査するには、 Java Flight Recorder を使用する方が便利です。
トレース
オンラインアプリケーションは通常、コードの変更をサポートしていませんが、 Javaで動的なインストルメンテーションを実行して問題を特定することが求められることがよくあります。そのため、btraceやarthas traceの使用は良い選択肢です。これらのツールは、アプリケーションプロセスを再起動することなく、トレースコードを動的に挿入できます。
フレームグラフ
Javaアプリケーションでフレームグラフを取得するのは面倒です。詳細についてはJava Flame Graphs の紹介: みんなに火を!参照してください。
結論
このドキュメントでは、データベースとやり取りする一般的なJavaコンポーネントに基づいて、TiDBを使用したJavaアプリケーション開発における一般的な問題と解決策について説明します。TiDBはMySQLプロトコルと高い互換性があるため、MySQLベースのJavaアプリケーションのベストプラクティスのほとんどがTiDBにも適用できます。
ヘルプが必要ですか?
不和またはスラック 、あるいはサポートチケットを送信するについてコミュニティに質問してください。