TiDBが転向する業務実戦

7856 ワード

著者:陳維、優品技術部RDを転送します.
冒頭
世界的なオープンソース分散データベースTiDBは2016年12月に第1版が正式に発表されて以来、業界内の多くの企業が徐々に導入し、広く認められている.
インターネット会社にとって、データストレージの重要性は言うまでもありません.NewSQLデータベースが登場する以前は、MySQLなどの単機データベースをストレージとして採用していたが、データ量の増加に伴い、「ライブラリ分割テーブル」は遅かれ早かれ直面する問題であり、MyCat、ShardingJDBCなどの優れたミドルウェアがあっても、「ライブラリ分割テーブル」はRDとDBAに高いコストをもたらす.NewSQLデータベースが登場すると、大量のデータに対するNoSQLの管理ストレージ能力だけでなく、従来のリレーショナルデータベースのACIDとSQLもサポートされているため、ビジネス開発にとってストレージの問題はより簡単で友好的になり、ビジネス自体に集中することができます.TiDBは、NewSQLの優れた代表です!
ビジネス開発の視点に立って、TiDBの最も魅力的ないくつかの特性は:
  • はMySQLプロトコルをサポートする(開発アクセスコストが低い);
  • 100%トランザクションをサポート(データ整合性の実現が簡単で信頼性が高い);
  • 無限レベル拡張(リポジトリ・テーブルを考慮する必要はありません).

  • これらの特性に基づいて、TiDBは業務開発において普及と実践に値するが、それは伝統的な関係型データベースではないので、関係型データベースのいくつかの使用経験と蓄積に対して、TiDBには違いがあり、主に「トランザクション」と「クエリー」の2つの面の違いを述べる.
    TiDBトランザクションとMySQLトランザクションの違い
    MySQLトランザクションとTiDBトランザクションの比較
    TiDBで実行されたトランザクションbは、インパクトバー数が1(修正に成功したと考えられる)であることを返しますが、コミット後のクエリでは、statusはトランザクションbが変更した値ではなく、トランザクションaが変更した値です.
    MySQLトランザクションとTiDBトランザクションには、次のような違いがあります.
    MySQLトランザクションでは、書き込み(または修正)が成功したかどうかの根拠として、エントリ数に影響を与えることができます.TiDBでは、これは不可能です!
    開発者として、次の問題を考慮する必要があります.
  • 同期RPC呼び出しで、戻り値を確認するためにインパクトバー数に厳格に依存する必要がある場合、どうすればいいですか?
  • マルチテーブルオペレーションでは、あるプライマリテーブルデータの更新結果に厳格に依存し、他のテーブルを更新(または書き込む)か否かの判断根拠とする必要がある場合、どうすればよいのでしょうか.

  • 原因分析と解決策
    MySQLでは、レコードを更新すると、そのレコードに対応する行レベルのロック(排他ロック)が先に取得され、取得に成功すると後続のトランザクション操作が行われ、取得に失敗するとブロックされて待機します.
    TiDBの場合、Percolatorトランザクションモデルを使用します.楽観的なロック実装として理解できます.トランザクションはオープンであり、トランザクションにはロックされません.コミット時にロックされます.この記事(TiDBトランザクションアルゴリズム)を参照してください.
    簡単な手順は次のとおりです.
    トランザクションのコミットのPreWriteフェーズでは、ロックチェックに失敗した場合:競合再試行をオンにすると、トランザクションのコミットは再試行されます.競合再試行がオンになっていない場合は、書き込み競合例外が放出されます.
    MySQLでは、書き込み操作に排他ロックが加わったため、パラレルトランザクションを論理的にシリアル化していることがわかります.一方、TiDBでは、楽観的なロックモデルに属し、トランザクションのコミット時にロックが追加され、トランザクションのオープン時に取得されたグローバルタイムスタンプがロックチェックの根拠として使用されます.
    したがって、TiDBトランザクションの違いをビジネスレベルで回避する本質は、ロック競合を回避することです.つまり、現在のトランザクションが実行されている場合、他のトランザクションタイムスタンプは生成されません(他のトランザクションが並列していません).トランザクションをシリアル化します.
    TiDBトランザクションのシリアル化
    ビジネス・レイヤでは、分散ロックを使用して、次のようにシリアル化できます.
    Springと分散ロックに基づくトランザクションマネージャの拡張
    Spring生態の下でspring-txでは、トランザクションの取得(getTransaction)、コミット(commit)、ロールバック(rollback)の3つの基本的な方法があるPlatformTransactionManagerという統一されたトランザクションマネージャインタフェースが定義されています.デザイナ・モードを使用すると、トランザクションのシリアル化コンポーネントは次のように設計できます.
    キーは次のとおりです.
  • タイムアウト時間:デッドロックを避けるために、ロックにはタイムアウト時間が必要です.ロックのタイムアウトによるトランザクションの並列化を回避するには、トランザクションにタイムアウト時間が必要であり、ロックのタイムアウト時間はトランザクションのタイムアウト時間より大きい必要があります(時間差は秒レベルが望ましい).
  • ロックタイミング:TiDBで「ロックチェック」の根拠は、トランザクションが開始されたときに取得される「グローバルタイムスタンプ」であるため、ロックタイミングはトランザクションが開始される前にしなければならない.

  • トランザクションテンプレートインタフェースの設計
    複雑なトランザクション書き換えロジックを非表示にし、簡単で友好的なAPIを暴露します.
    TiDBクエリとMySQLの違い
    TiDBの使用中には、いくつかのフィールドにインデックスが作成されている場合がありますが、インデックス検索を行わないほどクエリーが遅くなります.
    インデックス混同型(例)
    テーブル構造:
    CREATE TABLE `t_test` (
    	  `id` bigint(20) NOT NULL DEFAULT '0' COMMENT '  id',
    	  `a` int(11) NOT NULL DEFAULT '0' COMMENT 'a',
    	  `b` int(11) NOT NULL DEFAULT '0' COMMENT 'b',
    	  `c` int(11) NOT NULL DEFAULT '0' COMMENT 'c',
    	  PRIMARY KEY (`id`),
    	  KEY `idx_a_b` (`a`,`b`),
    	  KEY `idx_c` (`c`)
    	) ENGINE=InnoDB;
    

    クエリー:クエリー(a=1およびb=1)またはc=2のデータが必要な場合、MySQLでは、sqlはSELECT id from t_test where (a=1 and b=1) or (c=2);と書くことができ、MySQLがクエリーの最適化を行うと、idx_a_bidx_cの2つのインデックスが取得されます.しかし、TiDB(v 2.0.8-9)では、このsqlは遅いSQLになり、次のように書き換える必要があります.
    SELECT id from t_test where (a=1 and b=1) UNION SELECT id from t_test where (c=2);
    

    小結:この問題の原因は,TiDBのsql解析と最適化空間と理解できる.
    れいねつデータがた
    テーブル構造:
    CREATE TABLE `t_job_record` (
    	  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '  id',
    	  `job_code` varchar(255) NOT NULL DEFAULT '' COMMENT '  code',
    	  `record_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '  id',
    	  `status` tinyint(3) NOT NULL DEFAULT '0' COMMENT '    :0    ',
    	  `execute_time` bigint(20) NOT NULL DEFAULT '0' COMMENT '    (  )',
    	  PRIMARY KEY (`id`),
    	  KEY `idx_status_execute_time` (`status`,`execute_time`),
    	  KEY `idx_record_id` (`record_id`)
    	) ENGINE=InnoDB COMMENT='    job'
    

    データの説明:
    a.コールドデータ、status=1のデータ(処理済みデータ);
    b.ホットデータ、status=0 execute_time<= のデータ.
    スロー・クエリー:ホット・データの場合、データ量は一般的に大きくありませんが、クエリーの頻度は非常に高く、現在の(ミリ秒レベル)時間が154631579646であると仮定すると、MySQLでは、クエリーsqlは次のようになります.
    SELECT * FROM t_job_record where status=0 and execute_time<= 1546361579646
    

    このMySQLでは効率的なクエリーで、TiDBではインデックスから検索することもできますが、時間がかかります(百万レベルのデータ量、百ミリ秒レベル).
    原因分析:TiDBにおいて、下位インデックス構造はLSM-Treeであり、以下の図である.
    メモリレベルのC 0層からデータが照会されない場合、ハードディスク(HDD)の各層が層毎にスキャンされる.またmerge操作は非同期操作であり、インデックスデータの更新に一定の遅延があり、無効なインデックスがある可能性があります.階層単位のスキャンと非同期mergeのため、クエリーの効率が低下します.
    最適化方式:できるだけフィルタ範囲を縮小し、例えば非同期jobと組み合わせて記録周波数を取得し、データを漏らさないことを保証する前提の下で、execute_timeフィルタ区間を合理的に設定し、例えば1時間、sqlは以下のように書き換える.
    SELECT * FROM t_job_record  where status=0 and execute_time>1546357979646  and execute_time<= 1546361579646
    

    最適化の効果:10ミリ秒のレベル(以下).
    クエリーに関するヒント
    TiDBベースのビジネス開発では、従来のリレーショナル・データベースがもたらしたsqlの先入観を主とする理解や経験を捨て、DBAが提唱しているように、各sqlを慎重に設計します.sqlを設計する際には、必ず実行計画に注目し、必要に応じてDBAに教えてください.
    MySQLと比較して、TiDBの下位ストレージと構造はその特殊性と差異性を決定した.しかし、TiDBはMySQLプロトコルをサポートしており、TiDBで「プリコンパイル」や「バッチ」を使用するなど、パフォーマンスの向上にも共通しています.
    サービス側プリコンパイル
    MySQLでは、PREPARE stmt_name FROM preparable_stmを使用してsql文をプリコンパイルし、EXECUTE stmt_name [USING @var_name [, @var_name] ...]を使用してプリコンパイル文を実行できます.このように、同じsqlの複数回の動作により、従来のsqlよりも高い性能を得ることができる.
    mysql-jdbcソースコードでは、標準のStatementPreparedStatementを実現するとともに、ServerPreparedStatementを実現し、ServerPreparedStatementPreparedStatementの拡張に属し、3つの比較は以下の通りである.PreparedStatementStatementの違いは主にパラメータ処理にあり、パケットの送信に対して、サービス側を呼び出す処理ロジックは同じ(または類似)であることが容易に分かる.テストしたところ,両者の速度は同等であった.実は、PreparedStatementはサービス側の前処理ではありません.ServerPreparedStatementこそ本当のサービス側の前処理であり、速度もPreparedStatementより速い.シーンの使用は一般的に、頻繁なデータベースアクセスであり、sqlの数は限られています(キャッシュ・トーナメント・ポリシーがあり、使用すると2回IOになることはありません).
    バッチ処理
    複数のデータ書き込みの場合、通常のsqlはinsert … values (…),(…)である.一方、複数のデータ更新についても、update … case … when… then… endを用いてIO回数を減らすことができる.しかし、データの数が多ければ多いほど、sqlが複雑になり、sqlの解析コストも高くなり、時間のかかる増加は線形成長より高い可能性があります.バッチ処理は、単純なsqlを多重化し、バッチデータの書き込みまたは更新を実現し、システムにより低く、より安定した時間をもたらすことができる.
    バッチ処理の場合、クライアントとして、java.sql.Statementは主に2つのインタフェース方法を定義し、addBatchおよびexecuteBatchはバッチ処理をサポートする.
    バッチ処理の簡単な手順は次のとおりです.
    ビジネスでは、バッチ方式の書き込み(または更新)が使用され、従来のinsert … values(…),(…)(またはupdate … case … when… then… end)よりも性能が安定し、時間もかかる.