MySQL道普請便り

第156回pt-archiverを使って部分的にテーブルから情報を取得する

今までbackupに関しては、第153回 mysqlpumpを使ってバックアップを取ってみる第15回 mysqldumpを使ってバックアップするなどで紹介をしてきましたが、今回は第9回 pt-query-digestを使って遅いクエリーを発見する第116回 pt-upgradeを使ってインデックスチューニングの成果を確認するで紹介したPerocona Toolkitの1つ、pt-archiverを使って部分的に取得する方法を紹介します。

検証環境

今回は、第125回 phpMyAdminでDockerで建てたMySQLにアクセスするで記載したdocker-composeを利用して作成します。手元で簡単に試せるように、GitHubのわたしのレポジトリにサンプルコードとして置いてあるので、気軽に試したい方はgit cloneして試してみてください。試すにはdockerとdocker-composeが必要です。

pt-achiverを使用する

pt-achiverは、この連載でも何回か紹介しているPercona Toolkitのうちの1つで、MySQLのテーブルから別のテーブルやファイルとしてテーブルにある行を、文字通りアーカイブするためのツールになります。公式のドキュメントがあります。ぜひ一度よく読んで挙動を理解してから実行を行ってください。

では、pt-achiverを実際に実行してみましょう。今回は郵便番号データベースから北海道の行を取得してみます。

$ pt-archiver --souce u=root,p=password,h=127.0.01,D=zipcode,t=zipcode --file zipcode --no-delete --where "prefecture='北海道'"

さて、上記のように実行してみようとしたら、以下のようにエラーが発生してしまいます。

Cannot find an ascendable index in table at /usr/bin/pt-archiver line 3233.

こちらのエラー内容ですが、pt-achiverではINDEXや主キーが無いテーブルでは使用することができません。今回北海道の行を取得するので、それに合ったINDEXを以下のように貼りましょう。

root@267e35517618:/# mysql -uroot -ppassword zipcode
mysql> ALTER TABLE zipcode ADD INDEX index_prefecture(prefecture);

これでもう一度実行してみましょう。--sourceにはDSN形式で接続情報を記載します。今回はローカルのファイルにデータを取得するため--fileオプションで指定します。--no-deleteオプションですが、pt-archiverはアーカイブ済みの行をデータベース上から消してしまうため、消えてもらっては困る場合は必ずこのオプションを指定しましょう。とくに本番サーバに実行する場合には注意をして実行しましょう。

--whereオプションでWHERE句に指定したいWHERE句を書くことで取得ができます。

$ pt-archiver --source u=root,p=password,h=127.0.0.1,D=zipcode,t=zipcode --file zipcode --no-delete --where "prefecture='北海道'"
$ cat zipcode
01101   060     0600000 ホッカイドウ サッポロシチュウオウク    イカニケイサイガナイバアイ 北海道 札幌市中央区  以下に掲載がない場合
$

このように、取得できていることがわかります。mysqlコマンドラインクライアントで取得したときと同様に、タブ区切りの文字になっています。ただ、なぜか1件しか取得されていません。このように、想定外の挙動が起きた場合に便利なのが、--dry-runオプションです。こちらを着けて実行してみます。

$ pt-archiver --source u=root,p=password,h=127.0.0.1,D=zipcode,t=zipcode --file zipcode --no-delete --where "prefecture='北海道'" --dry-run
SELECT /*!40001 SQL_NO_CACHE */ `code`,`old_zipcode`,`zip_code`,`prefecture_kana`,`city_kana`,`town_kana`,`prefecture`,`city`,`town` FROM `zipcode`.`zipcode` FORCE INDEX(`index_prefecture`) WHERE (prefecture='北海道') ORDER BY `prefecture` LIMIT 1
SELECT /*!40001 SQL_NO_CACHE */ `code`,`old_zipcode`,`zip_code`,`prefecture_kana`,`city_kana`,`town_kana`,`prefecture`,`city`,`town` FROM `zipcode`.`zipcode` FORCE INDEX(`index_prefecture`) WHERE (prefecture='北海道') AND ((`prefecture` > ?)) ORDER BY `prefecture` LIMIT 1

上記のように、今回の指定オプションで実行されるSQLが出力されます。先ほど作成したINDEXがFORCE INDEXで利用されていることがわかります。そして最後にLIMIT 1がされているため、今回は1件しか取得されていません。北海道は、以下のようなクエリで8248件あることがわかりました。

mysql> select prefecture, COUNT(*) FROM zipcode where prefecture = '北海道';
+------------+----------+
| prefecture | COUNT(*) |
+------------+----------+
| 北海道     |     8248 |
+------------+----------+
1 row in set (0.01 sec)

北海道の8248件を--limitオプションで指定をして全部取得をしてみましょう。取得した値は上書きされてしまうので、1回zipcodeファイルを削除してからもう一度実行してみます。

$ rm zipcode 
$ pt-archiver --source u=root,p=password,h=127.0.0.1,D=zipcode,t=zipcode --file zipcode --no-delete --where "prefecture='北海道'" --limit 8248 --statistics
$ cat zipcode |wc -l
    8248

ということで、取得ができていることがわかります。

便利なオプションに関して

その他の便利なオプションとして、--statisticsオプションがあります。このオプションを付けて実行すると、以下のように実行時にどういう処理が行われたのかが確認できるようになってます。

$ pt-archiver --source u=root,p=password,h=127.0.0.1,D=zipcode,t=zipcode --file zipcode --no-delete --where "prefecture='北海道'" --limit 8248 --statistics

Started at 2021-10-01T12:48:55, ended at 2021-10-01T12:49:09
Source: D=zipcode,h=127.0.0.1,p=...,t=zipcode,u=root
SELECT 8248
INSERT 0
DELETE 0
Action          Count       Time        Pct
commit           8249    11.7163      89.55
print_file       8248     0.3384       2.59
select              2     0.0300       0.23
other               0     0.9989       7.64

データの取得時に一貫性を保ちたいこともあると思いますが、その場合は--for-updateオプションを付けてみると、SELECT ~ FOR UPDDATE 構文を利用してロックが取られるので指定しましょう。

kimurakouichirounoMacBook-Pro:for_training_sql koichiro.kimura$ pt-archiver --source u=root,p=password,h=127.0.0.1,D=zipcode,t=zipcode --file zipcode --no-delete --for-update --dry-run --where "prefecture='北海道'"
zipcode
SELECT /*!40001 SQL_NO_CACHE */ `code`,`old_zipcode`,`zip_code`,`prefecture_kana`,`city_kana`,`town_kana`,`prefecture`,`city`,`town` FROM `zipcode`.`zipcode` FORCE INDEX(`index_prefecture`) WHERE (prefecture='北海道') ORDER BY `prefecture` LIMIT 1 FOR UPDATE
SELECT /*!40001 SQL_NO_CACHE */ `code`,`old_zipcode`,`zip_code`,`prefecture_kana`,`city_kana`,`town_kana`,`prefecture`,`city`,`town` FROM `zipcode`.`zipcode` FORCE INDEX(`index_prefecture`) WHERE (prefecture='北海道') AND ((`prefecture` > ?)) ORDER BY `prefecture` LIMIT 1 FOR UPDATE

上記のようにdry-runをしてみると、後ろにFOR UPDATEが付与されていることがわかります。

また、--whereオプションは必須のオプションとなるため、全件の取得をしたい場合はどうしたら良いのか? と悩むこともあると思いますが、その場合は1=1のような必ずtrueになる条件式を入れてあげれば取得することができます。

mysql> select count(*) from zipcode;
+----------+
| count(*) |
+----------+
|   124340 |
+----------+
1 row in set (0.02 sec)

zipcodeの行数は124340行になるので、LIMITに指定して試してみましょう。

$ pt-archiver --source u=root,p=password,h=127.0.0.1,D=zipcode,t=zipcode --file zipcode --no-delete  --where "1=1"   --limit 124340
$ cat zipcode |wc -l
  124340

以上のように取得ができることがわかります。

最後に、--no-deleteオプションを付けないとどうなるかを確認していきたいと思います。

こちらのオプションは付けず実行してしまうと対象のDBから行が削除されてしまうという様に説明をしましたが、実際どうなってしまうか確認してみましょう。実行結果も確認がしたいので--statisticsオプションを付けて実行してみましょう。

$ rm zipcode
$ pt-archiver --source u=root,p=password,h=127.0.0.1,D=zipcode,t=zipcode --file zipcode --statistics  --where "1=1"   --limit 124340
Started at 2021-10-01T13:14:04, ended at 2021-10-01T13:23:09
Source: D=zipcode,h=127.0.0.1,p=...,t=zipcode,u=root
SELECT 124340
INSERT 0
DELETE 124340
Action          Count       Time        Pct
commit         124341   378.4032      69.43
deleting       124340   148.2469      27.20
print_file     124340     2.4886       0.46
select              2     0.3625       0.07
other               0    15.4997       2.84

deletingが実行されていることがわかります。MySQLに入って確認してみましょう。

mysql> select COUNT(*) from zipcode;
+----------+
| COUNT(*) |
+----------+
|        0 |
+----------+
1 row in set (0.01 sec)

ということで、データを取得した後のデータが無くなってしまっていることがわかります。実行する際には--no-deleteオプションが必要かどうか、必ずちゃんと確認してから実行をするようにしましょう。

まとめ

pt-archiverを使用すると、部分的にファイルを抜き出して利用するということがかんたんにできるので、行数がすごく多いテーブルから抽出して分析をしたい場合などに便利です。mysqlクライアントから抜き出して同様の事もできるのですが、NULLの処理など面倒なことが多いです。そのため、このpt-archiverを利用してデータの一部を取得して利用できるのは非常に便利です。

ただし、--no-deleteオプションを着け忘れて実行してしまうと、解説を行ったとおりDB上からデータが無くなってしまうため注意が必要です。本番に直接かけるような事は控えたほうが安全だと思います。

また、取得したファイルのロードには、LOAD DATA INFILE構文を使用する必要があるため、FILE権限が必要な事にも注意が必要です。

おすすめ記事

記事・ニュース一覧