三流エンジニアの落書き帳

さあ、今日も未知という扉を開けてみよう

mysqlクライアントの便利なpager

気づけば3月、今年はコロナウィルスの影響で参加しようと思っていたイベントが中止だったり、僕自身参加を自粛したりしてました。 この状況はいつまで続くんですかね・・・

さて今回は小ネタです。

皆さんはmysqlクライアントのpager機能を使っていますでしょうか?

現在設定されているpagerはstatusまたは¥sを実行することで確認できます。

mysql> status;
--------------
mysql  Ver 8.0.19 for Linux on x86_64 (Source distribution)

Connection id:      9
Current database:   
Current user:       root@localhost
SSL:            Not in use
Current pager:      stdout
Using outfile:      ''
Using delimiter:    ;
Server version:     8.0.19-debug Source distribution
Protocol version:   10
Connection:     Localhost via UNIX socket
Server characterset:    utf8mb4
Db     characterset:    utf8mb4
Client characterset:    utf8mb4
Conn.  characterset:    utf8mb4
UNIX socket:        /mysql/MyHome/tmp/mysql.sock
Binary data as:     Hexadecimal
Uptime:         21 hours 57 min 24 sec

Threads: 2  Questions: 19  Slow queries: 0  Opens: 157  Flush tables: 3  Open tables: 77  Queries per second avg: 0.000
--------------

Current pagerに現在のpagerが表示されていて、上の例ではstdoutつまり標準出力に設定されていることがわかります。

pagerについては、mysqlクライアントで実行したクエリの結果をパイプで渡す先になるものと考えるとわかりやすいのかなと思います。 例えば後述するlessコマンドをpagerに設定すれば結果をスクロールしながら確認することができます。

mysql> pager less

おそらく最もシンプルで使い勝手がいいのがlessコマンドではないでしょうか。

例えばあるデータベースに含まれているテーブル一覧を表示したいといったときに力を発揮します。データベースによっては含まれるテーブルが数百を超えることも珍しくないと思いますが、デフォルトのpagerだとその一覧が一気に画面に出力されてしまいます。

mysql> show tables from performance_schema;
+------------------------------------------------------+
| Tables_in_performance_schema                         |
+------------------------------------------------------+
| accounts                                             |
| cond_instances                                       |
| data_lock_waits                                      |
| data_locks                                           |
| events_errors_summary_by_account_by_error            |
| events_errors_summary_by_host_by_error               |
| events_errors_summary_by_thread_by_error             |
| events_errors_summary_by_user_by_error               |
| events_errors_summary_global_by_error                |
| events_stages_current                                |
| events_stages_history                                |
.
snip

このような場合に下記のようにpagerを設定すると長い一覧をスクロールや検索しながら確認することができます。

mysql> pager less
PAGER set to 'less'

またlessのオプションも同時に渡すことができます。 デフォルトだとlessコマンドを終了させると結果が残りませんが、-Xオプションを指定すると結果が残るようになるので便利です。

mysql> pager less -X
PAGER set to 'less -X'

lessには豊富なオプションがあるため自分が使いやすいと感じるpagerを設定するとよいでしょう。

Man page of LESS

なおオプション指定時などpagerに渡す引数が複数になるときはそれらをクォートで囲まないよう注意が必要です。

mysql> pager 'less -X'
PAGER set to ''less -X''

mysql> show tables from performance_schema;
sh: less -X: コマンドが見つかりません
103 rows in set (0.07 sec)

クォートで囲ったものをpagerに設定すると、それらを1つのコマンドとして認識してしまうため、上記のようにコマンドが見つからないといったエラーが出力されます。(ダブルクォーテーションで囲っても同じ事が起こります)

mysql> pager grep

grepもまた便利なpagerの一つです。

これを使うとWHERE句で指定しなくても特定のカラムを含む行のみ表示できたり、逆に特定のキーワードを含む文字列以外を表示できたりします。

僕はたまに下記のようなpagerを設定します。

mysql> pager grep -v Sleep
PAGER set to 'grep -v Sleep'

このpager、何が嬉しいかというと例えばなにか特定のクエリが原因でDBが詰まっているような状況でその原因のクエリを特定するのに便利です。

そのような状況では大抵の場合そのクエリを実行しているスレッドが複数存在しています。しかし、何百本も接続がある環境でshow processlistから確認するのは大変です。障害対応など急いでいるときは特に。

そんなときに上記のpagerを設定してあげるとshow processlistしただけでSleepになっていないすなわちRunningのスレッドだけ表示することができます。

もちろんinformation_schema.processlistからWHEREを駆使して同様の事ができますが、pagerを使うほうが圧倒的に速いはずです。

実際に僕もこれを使って障害対応をした経験があります。 非常におすすめです。

mysql> pager gzip -c > $DIR

これはあまり使う機会は多くないかもしれませんが、pagerの力を感じていただけるものだと思います。

mysql> pager gzip -c > /tmp/output.gz
PAGER set to 'gzip -c > /tmp/output.gz'

mysql> show tables from performance_schema;
103 rows in set (0.03 sec)

mysql> system zcat /tmp/output.gz | head
+------------------------------------------------------+
| Tables_in_performance_schema                         |
+------------------------------------------------------+
| accounts                                             |
| cond_instances                                       |
| data_lock_waits                                      |
| data_locks                                           |
| events_errors_summary_by_account_by_error            |
| events_errors_summary_by_host_by_error               |
| events_errors_summary_by_thread_by_error             |

これはSELECTした結果をgzipで圧縮をして/tmp下にoutput.gzという名前でファイルに吐いている様子です。

pagerを使って出力をしているのでSELECT結果以外にも応用が効きます。 例えば障害時点のshow processlistをとりあえずログとして残しておくといった使い方ができます。

pagerの設定と解除方法

毎回pagerを設定するのは面倒くさいのでよく使うpagerはmy.cnfに記述しておくと良いでしょう。

[mysql]
pager="less -n -i -F -X -E -R"

まだ現在のpagerを解除したいときはnopagerコマンドを実行することで解除できます。

mysql> pager grep root
PAGER set to 'grep root'

mysql> show processlist;
| 10 | root            | localhost | NULL | Query   |     0 | starting               | show processlist |
2 rows in set (0.00 sec)

mysql> nopager
PAGER set to stdout

mysql> show processlist;
+----+-----------------+-----------+------+---------+-------+------------------------+------------------+
| Id | User            | Host      | db   | Command | Time  | State                  | Info             |
+----+-----------------+-----------+------+---------+-------+------------------------+------------------+
|  4 | event_scheduler | localhost | NULL | Daemon  | 90429 | Waiting on empty queue | NULL             |
| 10 | root            | localhost | NULL | Query   |     0 | starting               | show processlist |
+----+-----------------+-----------+------+---------+-------+------------------------+------------------+
2 rows in set (0.00 sec)

まとめ

今回は小ネタでmysqlクライアントのpagerを見てきました。

pagerにはLinuxコマンドをそのまま渡せるので(もちろんパイプを使ってさらに広げることも可能)可能性は無限大です。

ぜひ便利なpagerを見つけてみてください。