「わかばちゃんとつくって、壊して、直して学ぶ NewSQL入門」をやっていく
はじめに
macOSでしか開発したことなかったけど、WindowsPCをもらったので以下WindowsにUbuntuをインストールしてやっていく!
WSL2インストールのところで一時間かかっちゃったけど、以下の記事を参考に動くようになったので始めていく!
Part1 - Chapter1 さわってみようNewSQL
NewSQLは、RDBMSのいいとこどりを狙ったもの!
システム上は、NoSQLのような分散データベースでありながら、使い手はそれを意識することなく従来のリレーショナルを使う感覚で利用できる。
リレーショナルとおなじでACID特性をもつ!
SQLを利用できるので、既存のリレーショナルデータベースからの移行も結構スムーズ。
最短で40日程度で移行できたよ~という話も聞いたことがある
個人的には、無停止でメンテナンスができるのもありがたいなと思う(ローリングアップデート)
Part1 - Chapter2 NewSQLクラスターを構築してみよう
TiDB/TiKV/TiFlash/PDなどの複数のコンポーネントを組み合わせて構築するのは知っていたけど、自分で構成するときはここら辺の調整もやらなくてはいけないのか・・・!という発見。(チューニングなどはこのコンポーネントたちを触っていくのかな?)
今回は、 TiUPというCLIツールをローカル構築して動かしていくらしい!
TiDBクラスターのそれぞれの役割
- TiDBノード…SQLを解釈してTiKVに伝える
- PDノード…各ノードの配置状況やクラスター全体の状況を収集・管理する
- TiKVノード…実際のデータを保管する
ノードごとノード数を増減できる!
TiDB Playgroundを使ってみる
MySQLクライアントをインストールしてクラスター接続してみたりする!
TiDB Playgroundコマンドでノード数を増減させたりする。
スケーラビリティを確認したい場合などコマンドを実行するだけでいいので、負荷テストなどが楽そう。
TiDBノードを二台にしてみた。
確認すると、tidbが二行になっている!プロセスが増える!
$ tiup playground display
Pid Role Uptime
--- ---- ------
1855 pd 2m40.8190811s
1863 tikv 2m40.8048554s
1884 tidb 2m40.7891885s
3173 tidb 37.8467387s
2077 tiflash 2m28.4436255s
ログとモニタリングの活用
Playgroundでリソース使用率や起動状況などが監視できる。
ノード数を増減させたり遊びつつ、最終的にはダッシュボードで見れるように!!
Part1 - Chapter3 SQLを学び、NewSQLクラスターで実行してみよう
SQLについての解説。
ここはすでに知っている部分なので、ビュンビュン読み進めていく
バージョン確認すると、TiDBってはいってる
mysql> SELECT VERSION();
+--------------------+
| VERSION() |
+--------------------+
| 8.0.11-TiDB-v8.5.2 |
+--------------------+
1 row in set (0.01 sec)
押さえておきたいポイント
- 連番の実装方法に工夫が必要なこともある(性能上の注意点など)
- 複数台のデータベースサーバーで構成されているため、採番処理の競合とその対応が必要
- RDBMSと高い互換性を持っているが、NewSQLの内部では分散処理を行っている。
- NewSQLでパフォーマンスを発揮できるようになるには、ここら辺の前提知識が大事
- 外部キーを設定している親と子テーブルが異なるサーバに配置されることがある
- パフォーマンスを向上させるために、親子テーブルを同じサーバーに配置することが可能
データのバックアップ方法
- バックアップ先はlocal://で始まるURLを指定する。
この時に、書き込み権限がないとえらーになるので、ディレクトリ権限をいじっておく
mysql> BACKUP DATABASE library to 'local:///tmp/backup/';
+----------------------+------+--------------------+---------------------+---------------------+
| Destination | Size | BackupTS | Queue Time | Execution Time |
+----------------------+------+--------------------+---------------------+---------------------+
| local:///tmp/backup/ | 7707 | 459538879891111939 | 2025-07-20 17:55:08 | 2025-07-20 17:55:08 |
+----------------------+------+--------------------+---------------------+---------------------+
バックアップに間違えまくって、SHOW BACKUP
するとこんな感じになっていた。
mysql> SHOW BACKUPS;
+------+----------------------+---------------+----------+---------------------+---------------------+---------------------+------------+------------------------------------------------------------------------------------------------------------------------+
| Id | Destination | State | Progress | Queue_time | Execution_time | Finish_time | Connection | Message |
+------+----------------------+---------------+----------+---------------------+---------------------+---------------------+------------+------------------------------------------------------------------------------------------------------------------------+
| 1 | local://tmp/backup/ | Wait Canceled | 100 | 2025-07-20 17:48:30 | 2025-07-20 17:48:30 | 2025-07-20 17:48:30 | 3328180232 | [executor:8124]Backup failed: mkdir /backup/: permission denied |
| 2 | local://tmp/backup/ | Wait Canceled | 100 | 2025-07-20 17:49:54 | 2025-07-20 17:49:54 | 2025-07-20 17:49:54 | 3328180232 | [executor:8124]Backup failed: open /backup/backup.lock.tmp.12fab589-7a6b-422c-aadd-fd09f452a793: permission denied |
| 3 | local://tmp/backup/ | Wait Canceled | 100 | 2025-07-20 17:53:56 | 2025-07-20 17:53:56 | 2025-07-20 17:53:56 | 3328180232 | [executor:8124]Backup failed: open /backup/backup.lock.tmp.c7959b00-401f-40bc-898b-32f80a82ab94: permission denied |
| 4 | local:///tmp/backup/ | Wait Canceled | 100 | 2025-07-20 17:54:28 | 2025-07-20 17:54:28 | 2025-07-20 17:54:28 | 3328180232 | [executor:8124]Backup failed: open /tmp/backup/backup.lock.tmp.188e6b65-5fe0-4714-8eb9-633562d0f05b: permission denied |
| 5 | local:///tmp/backup/ | Checksum | 100 | 2025-07-20 17:55:08 | 2025-07-20 17:55:08 | 2025-07-20 17:55:12 | 3328180232 | NULL |
+------+----------------------+---------------+----------+---------------------+---------------------+---------------------+------------+------------------------------------------------------------------------------------------------------------------------+
5 rows in set (0.00 sec)
- リストアするときは、
RESTORE DATABASE * FROM 'local:///tmp/backup';
mysql> RESTORE DATABASE * FROM 'local:///tmp/backup';
+---------------------+------+--------------------+--------------------+---------------------+---------------------+
| Destination | Size | BackupTS | Cluster TS | Queue Time | Execution Time |
+---------------------+------+--------------------+--------------------+---------------------+---------------------+
| local:///tmp/backup | 7707 | 459538879891111939 | 459538928456433871 | 2025-07-20 17:58:13 | 2025-07-20 17:58:13 |
+---------------------+------+--------------------+--------------------+---------------------+---------------------+
1 row in set (0.96 sec)
Part2 - Chapter4 NewSQLの処理の流れを見てみよう
ノード同士の細かいやりとりをみていく!
NewSQLの構成を知る
- コンピューティングノード(TiDB):ユーザーとの接続か確立・SQL解析→ストレージに問い合わせる
- ストレージノード(TiKV):ストレージ管理・問い合わせ用のAPI提供
- クラスター管理ノード(PD):各サーバーの配置を管理・メタデータや現在の状況、負荷状況を収集・管理する
TiDBもWebアプリケーションの構成とよく似ている。
(フロントエンドやAPIサーバーの前にロードバランサを配置する構成)
フロントエンド相当→TiDBサーバー
APIとDB→TiKVサーバー(兼務)
PDサーバーは、実際に利用するサーバを管理し、最適な状態を維持するのがお仕事。
処理の流れを追っていく
SELECTの場合:TiDB→PDへ問い合わせ→TiKBのデータ取得APIを呼び出し、データ取得→TiDBでデータ集約・結果生成→クライアントに結果を返す
PDサーバーは、どのTiKVサーバーにアクセスすればよいのかを考えている。
TiKBサーバーでは、リージョンと呼ばれるデータの塊をもっていて、このリージョン単位でTiKBにアクセスする。
ちなみに、リージョンは複数のレコードをPKの一定範囲で区切ったもの。
リージョンごとに負荷分散のために読み書きを担当するTiKVサーバーが決まっていて、これをリーダーと呼ぶ。
リージョンとリーダーの対応を管理してるのがPDサーバーになる。
じゃあなんでPDにリーダーを教えてもらう必要あるの?手間じゃない?遠回りじゃない?
→高速なレスポンスと一貫性のためらしい(うーんもっともらしい理由だけどふわっとしてる)
書き込まれたデータは複数台のTiKVサーバーにコピーされる
コピーから読み出すとコピー分のラグが生じる。だから、書き込まれたTiKVから読み出せば速いよね!って原理
PDサーバーはリーダーが偏らないように均等配置する。
負荷状況に応じてリージョンの範囲を分割して複数のリーダーで手分けできるようにしている。
メモ
TiDBサーバーを増やすこと→CPUの増加相当
TiKVサーバーを増やすこと→IOPSの増加相当
UPDATEの場合:TiDB(解析・最適化)→PDへ問い合わせ→TiKVでロック取得~書き込み~コピー~コピー→TiDBに結果を返す
書き込まれたデータを複数のTiKVサーバーにコピーする。
=耐障害性が高い!
タイムスタンプを理解(一番感動)
PDサーバーはタイムスタンプを発行している。
これは、処理の順番をつけたり読み出すデータを決定したりするのに利用している。
銀行の受付発券機の役割とおなじ
順番を定めることもできるし、トランザクションの同時実行制御にも重要な役割。
Part2 - Chapter5 1つずつ壊してみよう_1
Pidを使って、一つずつプロセスを停止していく
$ kill [pid]
TiDBを停止する
MySQLクライアントの接続先が失われてしまった状態。
$ tiup playground display
Checking updates for component playground... Pid Role Uptime
--- ---- ------
7414 pd 3m37.1644448s
7422 tikv 3m37.1490385s
7442 tidb 3m37.1309236s
7633 tiflash 3m18.8102352s
$ kill 7442
$ mysql --host 127.0.0.1 --port 4000 -u root
ERROR 2003 (HY000): Can't connect to MySQL server on '127.0.0.1:4000' (111)
TiDBを復旧させる。
(TiDBサーバーの台数を増やす)
$ tiup playground scale-out --db 1
To connect new added TiDB: mysql --comments --host 127.0.0.1 --port 4000 -u root -p (no password)
TiKVを落とす
TiKVはストレージノード。
TiKVはリージョンと呼ばれるかたまりでデータを保存している。
以下は、検索対象のリージョンが利用できないことを示している。
mysql> SELECT * FROM library.Books;
ERROR 9005 (HY000): Region is unavailable
TiKVが一台しかない場合に強制停止を行った場合は、データの不整合が生じている場合がある。
そのため、データを持たないTiDBサーバーのようにコマンドをつかって簡単に復旧ができない。
PDを落とす
PDはクラスター管理ノード。
PDはTSOを管理しているので、SQL実行のはじめにTiDBサーバーはPDサーバーに対してTSOを取得するようにAPIを呼び出す。
PDがダウンしていると、TSO取得の際にPDサーバーへのリクエストがタイムアウトしてしまうため、以下のエラーが出る。
mysql> SELECT * FROM library.Books;
ERROR 9001 (HY000): PD server timeout:
PDサーバーもTiKVサーバー同様にデータを保持している。
一台構成のPDサーバーを落とした場合、コマンドでの復旧は不可能。
まとめ
- 一台構成のばあい、非常に脆弱で、一つでも停止するとクラスター全体が利用できなくなる
- 特に、データを保持しているTiKVサーバーとPDサーバーが停止してしまうと、データに不整合が生じたり再開できなくなったりする可能性もある
- NewSQLでは各コンポーネントは複数台のサーバーで構成され冗長化されている
- ユーザーは台数指定だけを行い、障害時のフェイルオーバーやデータの同期などはすべてDBに任せることができる
- NewSQLでは、RDBMSでよくある構成のマスターレプリカ構成など、フェイルオーバー自体をユーザーが意識する必要をなくしてしまう。
Part2 - Chapter5 1つずつ壊してみよう_2
TiDBクラスターの場合の標準構成は、TiDBサーバー二台、TiKVサーバーが三台、PDサーバーが三台の構成をとる。
このような構成の場合、TiDBサーバーの前にロードバランサーが配置されるのが一般的。
ロードバランサ―はAWSのALB的なやつを使うのが一般的。
ローカル環境にはマネージドのロードバランサ―がないが、TiDB Playgroundであれば、ロードバランサーの代わりとなるTiProxyというコンポーネントが利用可能
コンポーネントの台数を指定するオプションをつけて実行していく
$ tiup playground --tiproxy 1 --db 2 --kv 3 --pd 3 --tiflash 0
Connect TiDB: mysql --comments --host 127.0.0.1 --port 4001 -u root
Connect TiDB: mysql --comments --host 127.0.0.1 --port 4000 -u root
Connect TiProxy: mysql --comments --host 127.0.0.1 --port 6000 -u root
TiDB Dashboard: http://127.0.0.1:2379/dashboard
Grafana: http://127.0.0.1:3000
TiKVとPDはRaftという分散合意アルゴリズムで整合性をとっている。
過半数のサーバーで実行されたかを判定する必要があるため、一般的には奇数台のサーバーを利用する。
本体は、別々のサーバーを利用してコンポーネント立ち上げる。
コンピューティングノードを停止する
まずは、二台あるTiDBのどちらに接続されて使っているかを調べる。
1.接続セッションの情報でserver_idの情報をみる
mysql> SHOW STATUS LIKE 'server_id';
+---------------+--------------------------------------+
| Variable_name | Value |
+---------------+--------------------------------------+
| server_id | f5d071c8-8268-4f25-9927-eea006e9317f |
+---------------+--------------------------------------+
1 row in set (0.01 sec)
サーバー情報は通常同様information_schemaから、、
mysql> SELECT ddl_id, ip, port FROM information_schema.tidb_servers_info;
+--------------------------------------+-----------+------+
| ddl_id | ip | port |
+--------------------------------------+-----------+------+
| 15280272-9839-4fe0-b7be-fc7e48a64fed | 127.0.0.1 | 4000 |
| f5d071c8-8268-4f25-9927-eea006e9317f | 127.0.0.1 | 4001 |
+--------------------------------------+-----------+------+
2 rows in set (0.00 sec)
このことから、port=4001のDBが利用されていることがわかる。
で、ここまでわかったけど、停止するプロセスが分かんないので、lsofコマンドを利用する。
OS上で開いているファイルと開いたプロセスを一覧するコマンド
待ち受け処理のpidは10377
になっているから、これ!
$ lsof -i:4001
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
tidb-serv 10377 gaki 29u IPv4 85062 0t0 TCP localhost:4001 (LISTEN)
tidb-serv 10377 gaki 44u IPv4 90207 0t0 TCP localhost:4001->localhost:57172 (ESTABLISHED)
tiproxy 10693 gaki 16u IPv4 84940 0t0 TCP localhost:57172->localhost:4001 (ESTABLISHED)
Pidを指定してコンポーネントをクラスターから削除する。
$ tiup playground scale-in --pid 10377
scale in tidb success
適当にクエリをうつ。
一度コネクションが切断され、MySQLクライアントが再接続している!
MySQLクライアントはコネクションの切断を検知すると、デフォルトでコネクションを再接続する。
mysql> SELECT VERSION();
ERROR 2013 (HY000): Lost connection to MySQL server during query
No connection. Trying to reconnect...
Connection id: 3
Current database: *** NONE ***
+--------------------+
| VERSION() |
+--------------------+
| 8.0.11-TiDB-v8.5.2 |
+--------------------+
1 row in set (0.00 sec)
更新の場合は・・・
mysql> INSERT INTO Books (title, author, publisher, published_year) values('test=title', 'test_author', 'test_publisher', 2025);
Query OK, 1 row affected (0.01 sec)
mysql> commit;
ERROR 2013 (HY000): Lost connection to MySQL server during query
No connection. Trying to reconnect...
Connection id: 4
Current database: library
Query OK, 0 rows affected (0.00 sec)
コミット前にDBが落ちると、元のトランザクションはロールバックされる。
NewSQLは再接続すれば処理の継続が可能。アプリケーション側でもリトライするような仕組みを入れると、恩恵をうけやすいかも
Part2 - Chapter5 1つずつ壊してみよう_3
ストレージノードを停止する
- データーが保存されているリージョン情報を取得して、各リージョンを一意に特定する
region_id
を把握する - リーダーノード、フォロワーノードの構成を調べる。region_idを持つリーダーノードを特定する
- リーダーノード情報を調べる(IP、ポート番号)
mysql> SELECT region_id, store_id, address FROM tikv_region_status NATURAL JOIN tikv_region_peers NATURAL JOIN tikv_store_status WHERE is_leader = 1 AND table_name = 'Books';
+-----------+----------+-----------------+
| region_id | store_id | address |
+-----------+----------+-----------------+
| 302 | 3 | 127.0.0.1:20161 |
+-----------+----------+-----------------+
1 row in set (0.01 sec)
- MySQLクライアントはTiDBサーバーに接続しているので、TiKVサーバーの停止の影響は受けない。
そのため、クエリを実行しても問題なく動作する。ロスコネとかの心配なし。 - 内部的にはリトライが行われるのでレイテンシは増加する
まとめ
- TiKVサーバーではデータはリージョンで保存される
- 一台のみ停止の場合は残りのフォロワーがリーダーを交代するので、影響を受けない
- 二台のリーダーを停止させると、自力での復旧ができなくなる。
- 実運用の場合は、一台目が停止した時点でスケールアウト操作を実施する
Part2 - Chapter5 1つずつ壊してみよう_4
PDは、基本的にスケールアウトやインを行わない。
通常は三台で運用されている。
PDの接続情報などは、pd-ctlというツールを利用する
$ tiup ctl:v8.5.1 pd member show
The component `ctl` version v8.5.1 is not installed; downloading from repository.
download https://tiup-mirrors.pingcap.com/ctl-v8.5.1-linux-amd64.tar.gz 450.02 MiB / 450.02 MiB 100.00% 16.64 MiB/s
Starting component ctl: /home/gaki/.tiup/components/ctl/v8.5.1/ctl pd member show
{
"header": {
"cluster_id": 7529417513757653070
},
"members": [
{
"name": "pd-1",
"member_id": 2234275743217008298,
"peer_urls": [
"http://127.0.0.1:2381"
],
"client_urls": [
"http://127.0.0.1:2382"
],
"deploy_path": "/home/gaki/.tiup/components/pd/v8.5.2",
"binary_version": "v8.5.2",
"git_hash": "4cd009c4db3c15215341a96521dd53e53c55e5bd"
},
{
"name": "pd-0",
"member_id": 3474484975246189105,
"peer_urls": [
"http://127.0.0.1:2380"
],
"client_urls": [
"http://127.0.0.1:2379"
],
"deploy_path": "/home/gaki/.tiup/components/pd/v8.5.2",
"binary_version": "v8.5.2",
"git_hash": "4cd009c4db3c15215341a96521dd53e53c55e5bd"
},
{
"name": "pd-2",
"member_id": 4060626905997471512,
"peer_urls": [
"http://127.0.0.1:2383"
],
"client_urls": [
"http://127.0.0.1:2384"
],
"deploy_path": "/home/gaki/.tiup/components/pd/v8.5.2",
"binary_version": "v8.5.2",
"git_hash": "4cd009c4db3c15215341a96521dd53e53c55e5bd"
}
],
"leader": {
"name": "pd-0",
"member_id": 3474484975246189105,
"peer_urls": [
"http://127.0.0.1:2380"
],
"client_urls": [
"http://127.0.0.1:2379"
],
"deploy_path": "/home/gaki/.tiup/components/pd/v8.5.2",
"binary_version": "v8.5.2",
"git_hash": "4cd009c4db3c15215341a96521dd53e53c55e5bd"
},
"etcd_leader": {
"name": "pd-0",
"member_id": 3474484975246189105,
"peer_urls": [
"http://127.0.0.1:2380"
],
"client_urls": [
"http://127.0.0.1:2379"
],
"deploy_path": "/home/gaki/.tiup/components/pd/v8.5.2",
"binary_version": "v8.5.2",
"git_hash": "4cd009c4db3c15215341a96521dd53e53c55e5bd"
}
}
メンバーの一覧と、PDのリーダーがわかる。
PDリーダーのclient_urlsで表示されているポートがPDサーバーのAPI呼び出しに利用されるポート。
このポートからPidを引き当てる
$ lsof -i:2379 | grep LISTEN
pd-server 11205 gaki 8u IPv4 116480 0t0 TCP localhost:2379 (LISTEN)
このPDサーバーを停止しても、SELECTの実行には問題ない。
だが、デフォルトポートを停止しちゃったので、このポートは利用できずに、情報を見ることができなくなっている。
$ tiup ctl:v8.5.1 pd member show
Starting component ctl: /home/gaki/.tiup/components/ctl/v8.5.1/ctl pd member show
Failed to get pd members: Get "http://127.0.0.1:2379/pd/api/v1/members": dial tcp 127.0.0.1:2379: connect: connection refused
なので、このコマンドにオプションをつけて、別PDに接続してあげる!
$ tiup ctl:v8.5.1 pd member show --pd http://127.0.0.1:2382
Starting component ctl: /home/gaki/.tiup/components/ctl/v8.5.1/ctl pd member show --pd http://127.0.0.1:2382
まとめ
- PDサーバーは通常三台で動き、その中の一台がリーダーとして機能する
- 一台のPDサーバーを停止しても、残りからリーダーに選出(昇格?)されるので正常稼働する
- 二台のPDサーバーを停止すると過半数が失われ、クラスターが稼働できなくなる
mysql> SELECT * FROM Books;
ERROR 9001 (HY000): PD server timeout:
Part3 - Chapter6 NewSQLの仕組み アーキテクチャを理解しよう
- 実データが格納されるストレージはキーバリューの形で保存している
コンピューティング層(TiDBノード)
- SQLパースと解析
- 字句解析と構文解析を経てASTへと変換
- 文字列→論理的なデータ構造に整理し、後続の最適化処理を行いやすくする
- 最適化と実行計画の生成(ここらへんはMySQLサーバーと同じかな)
- ステートレスな設計
- TiDB上にはデータを保存しない
- そのため、クライアントからの大量接続に対して簡単にスケールアウトできる
ストレージ層
TiKVがストレージ層に該当する
- データの永続化
- 分散データの管理
- 分散合意アルゴリズムによって担保
- 分散トランザクションとMVCC
それぞれの役割分担によって、スケールアウト・耐障害性・柔軟なデータ管理を実現している
mysql> SELECT TYPE, INSTANCE, START_TIME, UPTIME FROM cluster_info;
+---------+-----------------+---------------------+---------------+
| TYPE | INSTANCE | START_TIME | UPTIME |
+---------+-----------------+---------------------+---------------+
| tidb | 127.0.0.1:4001 | 2025-07-21 20:19:21 | 2m11.7986224s |
| tidb | 127.0.0.1:4000 | 2025-07-21 20:19:21 | 2m11.7986272s |
| tidb | 127.0.0.1:4002 | 2025-07-21 20:19:21 | 2m11.7986291s |
| pd | 127.0.0.1:2382 | 2025-07-21 20:19:11 | 2m21.7986298s |
| pd | 127.0.0.1:2379 | 2025-07-21 20:19:11 | 2m21.7986304s |
| pd | 127.0.0.1:2384 | 2025-07-21 20:19:11 | 2m21.798631s |
| tikv | 127.0.0.1:20162 | 2025-07-21 20:19:13 | 2m19.7986316s |
| tikv | 127.0.0.1:20161 | 2025-07-21 20:19:13 | 2m19.7986321s |
| tikv | 127.0.0.1:20160 | 2025-07-21 20:19:13 | 2m19.7986327s |
| tiproxy | 127.0.0.1:6000 | 2025-07-21 20:19:11 | 2m21.7986332s |
+---------+-----------------+---------------------+---------------+
10 rows in set (0.01 sec)
TiDBでのクエリの最適化処理
- 論理的最適化
- 使われていないカラムやクエリを除外
- 条件式を早い段階で適用
- サブクエリを単純な結合に変換
- 外部結合を内部結合に変える など
- 物理最適化
- テーブル結合において最適な方法を選ぶ
- テーブルスキャンorインデックススキャンどちらを使うか選ぶ
- 結合するテーブルの順序を調整・全体の処理効率を高める
- 処理を並列化
あとは実行計画を生成するが、ここらへんはMySQLとほぼ似ている
実際に実行計画をみるとこんな感じになっている。
mysql> EXPLAIN SELECT * FROM users WHERE age > 20;
+-------------------------+----------+-----------+---------------+-----------------------------------+
| id | estRows | task | access object | operator info |
+-------------------------+----------+-----------+---------------+-----------------------------------+
| TableReader_7 | 3333.33 | root | | data:Selection_6 |
| └─Selection_6 | 3333.33 | cop[tikv] | | gt(chapter6_2_5_db.users.age, 20) |
| └─TableFullScan_5 | 10000.00 | cop[tikv] | table:users | keep order:false, stats:pseudo |
+-------------------------+----------+-----------+---------------+-----------------------------------+
3 rows in set (0.00 sec)