GA

2017/11/28

MySQLから大量のレコードをちまちま削除するメモ

想定ケースとしては1億行くらいのテーブルから7000万行くらい消すクエリーを、レプリケーションが遅れずバッファプールも食い切らない程度にちまちま消すようにする。
DELETE FROM t1 WHERE hoge = 1 AND last_update < '2017-11-28'
「ちまちま削除する」なので、トランザクションでAll or Nothingを保証したい場合は使えない。 id をプライマリーキー(ただしサロゲートキーかどうかは問わない)、 hoge, last_update が本来消し込みに使いたいカラムだとする。 プライマリーキー(またはユニークキー)がないテーブルのことは考えない。

KEY(hoge, last_update) がある場合

  • ターゲットのプライマリーキーを取り出して DELETE .. WHERE id IN .. の形に落とし込む
  • 行ロックに落とし込める
  • idの型を選ばない(varcharだろうと使える)
  • 自前でINリストを作るのが面倒ならGROUP_CONCATという手もあるけどその場合は group_concat_max_len に注意
  • DELETE の方でもとの条件をANDしておくのを忘れると事故ることがある。。
  • プライマリーキーに対するWHEREがあるので実行計画で暴発しにくい…?
    • けどあんまりINのリストを長くするとテーブルスキャン選びやがった on 5.7
SELECT id FROM t1 WHERE hoge = 1 AND last_update < '2017-11-28' ORDER BY id LIMIT 1000;
DELETE FROM t1 WHERE id IN (.., ..) AND hoge = 1 AND last_update < '2017-11-28';

SELECT id FROM t1 WHERE hoge = 1 AND last_update < '2017-11-28' ORDER BY id LIMIT 1000;
DELETE FROM t1 WHERE id IN (.., ..) AND hoge = 1 AND last_update < '2017-11-28';

..

(KEY(hoge) のみがある、または削除に使えるキーがない) && idが数値型の場合

  • idのレンジを細かく区切ってループさせる
  • 最終的には全レコードにアクセスしないといけないので一度にやるとバッファプールが荒れる。適度にsleepを入れること
  • ネクストキーロックなので、DELETEしてる範囲(+α)にINSERTやUPDATEが来るとブロックされる
DELETE FROM t1 WHERE hoge = 1 AND last_update < '2017-11-28' AND id BETWEEN 1 AND 1000;
DELETE FROM t1 WHERE hoge = 1 AND last_update < '2017-11-28' AND id BETWEEN 1001 AND 2000;
DELETE FROM t1 WHERE hoge = 1 AND last_update < '2017-11-28' AND id BETWEEN 2001 AND 3000;
..

KEY(hoge) のみがある && idが文字列型の場合

  • idの最小値を取ってORDER BY id LIMITで少しずつ行く
  • 最終的には全レコードにアクセスしないといけないので一度にやるとバッファプールが荒れる。適度にsleepを入れること
  • ネクストキーロックなので、DELETEしてる範囲(+α)にINSERTやUPDATEが来るとブロックされる
  • 実行計画が暴発すると地獄が見えるのでDELETEの方はUSE INDEXした方が良さげ。DELETEでUSE INDEXするには テーブルリファレンス構文 の方を使う
  • 正直この辺まで来るとtsvにでも吐き出させて WHERE id IN (..) の形にした方が良いような気がする
SELECT MIN(id) AS min_id FROM t1 WHERE hoge = 1 AND last_update < '2017-11-28' ;
DELETE t1 FROM t1 USE INDEX(PRIMARY) WHERE id >= $min_id AND hoge = 1 AND last_update < '2017-11-28' ORDER BY id LIMIT 1000;

SELECT MIN(id) AS min_id FROM t1 WHERE hoge = 1 AND last_update < '2017-11-28' ;
DELETE t1 FROM t1 USE INDEX(PRIMARY) WHERE id >= $min_id AND hoge = 1 AND last_update < '2017-11-28' ORDER BY id LIMIT 1000;
..

削除に使えるキーがなくてidが文字列型の場合

  • 悔い改めなさい
  • 一度tsvにでも吐き出させて WHERE id IN (..) の形に落とし込む
  • SELECTで1回テーブルスキャン、これでバッファプールが荒れそう。ぶん回しても良いバックアップとかバッチ用とかのMySQLがあればここが無視できるからこのやり方でも安定しそう
SELECT id FROM t1 WHERE hoge = 1 AND last_update < '2017-11-28'  INTO OUTFILE '/tmp/target.txt; /* これが時間かかるはずなのでたっぷり後悔できる */

DELETE FROM t1 WHERE id IN (.., ..) AND hoge = 1 AND last_update < '2017-11-28'; /* お好みの言語でファイルを処理してINリストを作る */
DELETE FROM t1 WHERE id IN (.., ..) AND hoge = 1 AND last_update < '2017-11-28';
..
どうでしょう。

【2021/05/21 18:31】
MySQL 8.0では取れる手が増えていてちょっと感動した。

2017/11/06

mysqlbinlog: [ERROR] unknown variable 'default-character-set=utf8mb4' と言われないためのTIPS

前にいつかどこかでメモした気がしなくもないけれど見つからなかったので。  ⇒ 書き上げてから思い出した、 第33回 MySQLのオプションファイル my.cnfの豆知識[その2]:MySQL道普請便り だ。。

default-character-setmysql コマンドラインクライアントとか mysqldump あたりで使う、クライアントの文字コードを指定するためのオプション。
MySQL 5.0以前ではサーバー側のデフォルトの文字コードを指定するオプションも同じ名前だったけど、現在ではそっちは character-set-server に名前が変わっている。 MySQL 5.0では互換性のためにどちらの名前も使えたけれど、5.1で猶予期間が終わって大量の秘伝のタレを死に追いやったのはもはや懐かしい話。。
で、タイトルのエラーが出るような my.cnf
[client]
default-character-set=utf8mb4
と書いてあるケースがほとんどだと思う( [mysqlbinlog] セクションにわざわざ自分で default-character-set を書いたりしないだろうから)
[client] セクションは割と便利で、コマンドラインクライアントのほとんどがコンフィグファイルからそのセクションを読んでくれる。逆を言うと、 [client] セクションを読んでしまうプログラムが対応していないオプションを [client] セクションに書いてしまうと、タイトルのようなエラーが出ることになる。
どのプログラムがどのセクションを読むかは、ソースコードから load_default_groups でgrepするとぽこぽこ出てきたりする。 [client] セクションを読むやつだけだっと抜粋。
./client/check/mysqlcheck.cc:static const char *load_default_groups[] = { "mysqlcheck", "client", 0 };
./client/dump/program.cc:const char *load_default_groups[]=
{
  "client", /* Read settings how to connect to server. */
  "mysql_dump", /* Read special settings for mysql_dump. */
  0
};

./client/mysql.cc:static const char *load_default_groups[]= { "mysql","client",0 };
./client/mysql_secure_installation.cc:static const char *load_default_groups[]= { "mysql_secure_installation", "mysql", "client", 0 };
./client/mysqladmin.cc:static const char *load_default_groups[]= { "mysqladmin","client",0 };
./client/mysqlbinlog.cc:static const char *load_default_groups[]= { "mysqlbinlog","client",0 };
./client/mysqldump.c:static const char *load_default_groups[]= { "mysqldump","client",0 };
./client/mysqlimport.c:static const char *load_default_groups[]= { "mysqlimport","client",0 };
./client/mysqlshow.c:static const char *load_default_groups[]= { "mysqlshow","client",0 };
./client/mysqlslap.cc:static const char *load_default_groups[]= { "mysqlslap","client",0 };
./client/mysqltest.cc:static const char *load_default_groups[]= { "mysqltest", "client", 0 };
./client/upgrade/program.cc:const char *load_default_groups[]=
{
  "client", /* Read settings how to connect to server */
  "mysql_upgrade", /* Read special settings for mysql_upgrade*/
  0
};

./extra/resolveip.c:/*static char * load_default_groups[]= { "resolveip","client",0 }; */
結構色々出てくる。 ほとんどのものはそう滅多に使わないし、正直これを全部考慮するのは無理ゲーなので、ワーニングを許容するなら loose接頭辞 と組み合わせて設定するのがオススメだ。
[client]
loose-default-character-set=utf8mb4
このように loose 接頭辞をつけておくと、 default-character-set を理解しない mysqlbinlogmysqlslap でもエラーでアボートせずにワーニングだけ出力して勘弁してくれる。
$ mysqlslap
mysqlslap: [Warning] unknown variable 'loose-default-character-set=utf8mb4'
参考までにウチのmy.cnfからクライアント用のセクションだけ引っこ抜いたのはこんな感じだった。
[client]
port= __port__ #<<
socket= __datadir__/mysql.sock #<<
loose-default-character-set= utf8mb4

[mysqldump]
quick
max_allowed_packet= 1G
single-transaction
#lock-all-tables
events
routines
triggers
master-data=2
#dump-slave=2

[mysql]
no-auto-rehash
show-warnings
prompt= "__hostname__ [\d]> " #<<
#safe-updates
syslog

今日のネタ提供は @purple_jwl さんでした。ごちそうさまでした。






2017/10/30

MySQL 8.0.0で予約語だったROLEが8.0.1ではキーワードになっていたはなし

TL;DR

  • hmatsu47 さんに MySQLのキーワードと予約語 の中で「ROLEは予約語じゃなくてキーワード」と教えてもらった
  • 予約語とキーワードの違いは、 role_or_ident_keyword にリストされているかどうか? (ちなみに5.7だと単に keyword ってリストになってる)

シンボルのリスト

ラベルのリスト

ほげってみる

$ diff -c1 sql/sql_yacc.yy.orig sql/sql_yacc.yy
*** sql/sql_yacc.yy.orig        2017-09-19 20:33:50.000000000 +0900
--- sql/sql_yacc.yy     2017-10-30 16:23:04.657625949 +0900
***************
*** 13343,13345 ****
          | RESTORE_SYM           {}
-         | ROLE_SYM              {}
          | ROLLBACK_SYM          {}
--- 13343,13344 ----

$ make && make install

mysql80 8> SELECT @@version;
+--------------------+
| @@version          |
+--------------------+
| 8.0.3-rc-debug-log |
+--------------------+
1 row in set (0.00 sec)

mysql80 8> CREATE TABLE t1 (role int);
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'role int)' at line 1
転けるようになった。

なんというか

予約語のキーワードと非予約語のキーワードの境目って案外大したことなかった…。 これを機に色々ゴニョって遊んでみ(ない

2017/10/04

ファイルを吐かないtarコマンドの進捗を確認する

ファイルを吐かないというのは、MySQLのdatadirをtarボールに固めながら圧縮してS3にアップロードするようなケース(なんて限定的)
$ tar -C /var/lib -c mysql | pzstd -qc | aws s3 cp - s3://...
S3さん、アップロード終わるまで状況が見えないので、保管先のファイルサイズから全体をざっくり見積もることができなかった。俺のやり方が悪いだけかも知れない。
取り敢えず lsof で今掴んでいるファイルを見ることくらいまではぱっと思い付いた。
$ ps auxww | grep tar
root      1181  0.0  0.4 289208  4800 ?        Ssl  23:15   0:00 /usr/bin/docker-containerd-current -l unix:///var/run/docker/libcontainerd/docker-containerd.sock --shim docker-containerd-shim --metrics-interval=0 --start-timeout 2m --state-dir /var/run/docker/libcontainerd/containerd --runtime docker-runc --runtime-args --systemd-cgroup=true
yoku0825  3259  0.3  0.1 123344  1232 pts/0    S+   23:22   0:00 tar c data
yoku0825  3263  0.0  0.0 112644   968 pts/1    R+   23:22   0:00 grep --color=auto tar

$ lsof -p 3259
COMMAND  PID     USER   FD   TYPE DEVICE  SIZE/OFF      NODE NAME
..
tar     3259 yoku0825    0u   CHR  136,0       0t0         3 /dev/pts/0
tar     3259 yoku0825    1w  FIFO    0,8       0t0     28213 pipe
tar     3259 yoku0825    2u   CHR  136,0       0t0         3 /dev/pts/0
tar     3259 yoku0825    3r   DIR  253,0      4096  71088914 /usr/mysql/5.7.19/data
tar     3259 yoku0825    4r   REG  253,0  21091934  68051378 /usr/mysql/5.7.19/data/bin.000011
たとえば今は bin.000011 を掴んでる、というところまではわかる。
が、 tar コマンドってファイルをどういう順番で掴むのかがわからず、bin.000011 が全体で何番目のファイルなのかよくわからない。 経験則として、 tar cvf とか tar xvf でファイル名だだーっと流している時でも、少なくともファイルパスでソートされているようには見えない。
困った時のソースコード。
ディレクトリーをターゲットにtarボールを作る時には create_archivedump_filedump_file0dump_dirget_directory_entries と来て、その中からgnulibstreamsavedir を呼び出している。
streamsavedir がやってることは単に readdir しているだけで、この時の並び順は特に決まった規則がある訳ではないらしい。
ところで readdir したものをそのままといえば、ls のソート無しオプションである -f な訳で、
$ ls -fl 
total 239212
drwxr-x---  9 yoku0825 yoku0825     4096 Oct  3 23:10 .
drwxrwxr-x 11 yoku0825 yoku0825      148 Jul 18 15:13 ..
-rw-r-----  1 yoku0825 yoku0825 79691776 Oct  3 23:10 ibdata1
-rw-r-----  1 yoku0825 yoku0825       56 Jul 18 15:12 auto.cnf
drwxr-x---  2 yoku0825 yoku0825     4096 Aug 22 18:29 mysql
...
drwxr-x---  2 yoku0825 yoku0825     4096 Aug 10 15:10 i_s
drwxr-x---  2 yoku0825 yoku0825     8192 Aug 10 15:10 p_s
-rw-r-----  1 yoku0825 yoku0825  3818805 Aug 18 18:52 bin.000006
-rw-r-----  1 yoku0825 yoku0825 21091934 Aug 24 18:41 bin.000011
-rw-r-----  1 yoku0825 yoku0825 48221936 Sep  6 12:44 bin.000012
-rw-r-----  1 yoku0825 yoku0825    86553 Sep 15 15:47 bin.000017
-rw-r-----  1 yoku0825 yoku0825    10626 Sep 20 16:30 bin.000024
-rw-r-----  1 yoku0825 yoku0825      169 Aug 22 14:26 relay.000001
-rw-r-----  1 yoku0825 yoku0825      209 Sep 12 13:16 bin.000015
-rw-r-----  1 yoku0825 yoku0825      227 Sep 19 20:49 bin.000022
大体残り50MBくらいかなあと。ディレクトリー階層があるところなら親ディレクトリーがどの場所に並んでて、そのディレクトリーの中でファイルが何番目に位置しているか…とか丁寧に調べれば大体今何%のところにあるかは出せるような気がする。
とはいえまあ pipe viewer 使えばいいよねとは思う。
$ tar -C /var/lib -c mysql | pv | pzstd -qc | aws s3 cp - s3://...
 247MiB 0:00:04 [50.2MiB/s] [         <=>
pv 使うの忘れた時とかcronからキックされた時用かな。

2017/09/13

Test::More::Colorでtoku_bassさんに助けてもらったはなし

TL;DR

@toku_bass さんありがとうございます!!!!1

今から24時間ほど前に 日々の覚書: Test::More::Colorが息してなくてつらい というエントリーを書いた。
そして24時間後の今、

tokubass/p5-Test-More-Color-SupportTest2

あなたが!!!! 神か!!!!
$ perl -MTest::More -M"Test::More::Color qw{foreground}" -E 'say $Test::More::VERSION; ok(1)'
1.302096
ok 1
# Tests were run but no plan was declared and done_testing() was not seen.





( ´-`).oO(あっテキストだとこの感動が伝わらない



「困ったなあ(自分では直せないけど)」から誰かに助けてもらえるのって本当に嬉しい。

ありがとうございます!
@toku_bass ++ !!

2017/09/12

Test::More::Colorが息してなくてつらい

TL;DR
最近 Test::More::Color で上手く色がつかない(´・ω・`)

古いのを引っ張り出してきて試してみるけれど、残っているもののうち Test::Simple-1.001014だとOK(色が付く)で、Test::Simpe-1.302086 だとNG(色が付かない)
Test2を使うようにしたこのコミットで、 _print が削除されてるので、v1.302013_001 以降のバージョンではTest::More::Colorが息をしなくなってる。
割と色んなものをTest::More::Color使ってバシバシ色を付けていたので、どうしようかなぐぬぬ。
個人的にはもうどこをフックさせればいいのか全然わからないので仕方なくTest::Simple-1.001014をインストールしてつかっています(´・ω・`)

2017/08/12

数秒おきに ERROR: 2006 MySQL server has gone away でmysqlコマンドラインクライアントの接続が切れる

はじまりは @uessy_akrさん のツイート。






“MySQL Server has gone away” はクライアント側のエラー CR_SERVER_GONE_ERROR で、「クライアントは接続張りっぱなしのつもりなのに次のクエリーを投げたら実はサーバー側から接続を切られていた」時に出るエラー。
よって原因になり得るのは
  • タイムアウトが非常に短く設定されていて、クエリーの間隔がサーバー側タイムアウトに引っかかっている
  • 非常に短い間隔でmysqldがダウンしている
    • サーバープロセスがクラッシュすれば当然、接続していたコネクションは破棄されるから。
      • で、mysqld_safeがすぐにmysqldを再起動するので直後の接続は成功して、またクラッシュするやーつ。
    • 地雷erにはこっちの方が馴染みがある。。
    • SHOW STATUS LIKE 'uptime'ps でmysqldの起動時刻を見たり、エラーログを見たりするのもいい。
  • ネットワークなどに問題があり、しょっちゅう接続が(中間のどこかで)プチプチ切れている
    • 体験したことないけど可能性としては一応
あたり。
で、なんかどうもどうやら






mysqldが毎回クラッシュしている方のケースだったらしい。。
signal 6SIGABRT で、MySQL内部のAssertに引っかかるとこれで落ちる。 ちなみに This could be because you hit a bug. は単なる(?)決まり文句で必ず言われるのであんまり関係ない。 体感で一番起きやすいのはInnoDBのページ破損でAssertに引っかかって食らうことが多い印象。
どうやら大正解だったっぽい。




バックアップがある場合はそこから切り戻すのが一番手っ取り早いし、それができない(最新の状態が壊れたところにしかないとか)場合は innodb_force_recovery を駆使して無理矢理データを引っこ抜くしかない。
この手順はここが丁寧(クラッシュしてるわけじゃないからちょっと状況は違うけど、「解決策 - I. InnoDB の修復」が修復方法に当たる)
( ´-`).oO(あれ、他所へのリンクを貼りまくってるだけの記事になってしまった…