2017年12月8日金曜日

mikasafabric for MySQLのつらいところ

この記事は MySQL Casual Advent Calendar 2017 の8日目の記事です!
1週間前の記事、 日々の覚書: これが多分最後の「MySQL Fabricつらい」 でお焚き上げをしたMySQL Fabricですが、 世の中の物好きな会社 がMySQL Fabricをフォークして mikasafabric for MySQL として使っています。
今日はそのmikasafabric つらい つらくない話をします。ハンカチの用意はよろしいでしょうか。

取り敢えずぶっちゃけた話をするとPythonつらい。
  • どこにクローズ漏れがあるのか ファイルディスクリプターをリーク する
    • max_open_files の値まで突っ込む
    • 監視が Too many open files で転ける
    • フェイルオーバーしようとする
    • フェイルオーバー操作も Too many open files で転ける
    • しかもタイミングによっては open_files に空きができるのでフェイルオーバー操作が 一部だけ成功する という
_人人人人人人人人人人人_
> 控えめに言って地獄 <
 ̄Y^Y^Y^Y^Y^Y^Y^Y^ ̄
接続しているMySQL Routerの数にある程度依存しているような気がしたので、 MySQL Procotolをしゃべっているところ をゴニョゴニョしているものの改善せず…。ボスけて。

↑のバグを踏んだ時、 Too many open files で処理が中途半端に転けたがためにクラスターとして変な状態になっちゃって、「バッキングストアのレコードを直接書き換えることによってフェイルオーバー」させる実績を解除した。つらい。
誰の役にも立たないと思いますが、 servers.modeservers.status をUPDATEするだけでは足りなくて、 groups.master_uuid もUPDATEしてあげないと promote がAssertion Failureで転けます。 しかもバッキングストアに永続化されているので何度再起動しても転けます。これを知らずにこの状態に陥った場合、大した台数でなければ teardown して再登録した方が速いかも知れません。
mysql> SELECT * FROM servers;
+--------------------------------------+-----------------+------+--------+--------+----------+
| server_uuid                          | server_address  | mode | status | weight | group_id |
+--------------------------------------+-----------------+------+--------+--------+----------+
| a95d3771-d667-11e7-acc6-0242ac110002 | 172.17.0.2:3306 |    3 |      3 |      1 | myfabric |
| ab07f0b9-d667-11e7-abd0-0242ac110003 | 172.17.0.3:3306 |    1 |      2 |      1 | myfabric |
| ae8c0955-d667-11e7-ad65-0242ac110004 | 172.17.0.4:3306 |    1 |      2 |      1 | myfabric |
+--------------------------------------+-----------------+------+--------+--------+----------+
3 rows in set (0.00 sec)

mysql> SELECT * FROM groups;
+----------+-------------+--------------------------------------+----------------------------+--------+
| group_id | description | master_uuid                          | master_defined             | status |
+----------+-------------+--------------------------------------+----------------------------+--------+
| myfabric | NULL        | a95d3771-d667-11e7-acc6-0242ac110002 | 2017-12-01 07:17:18.000000 |       |
+----------+-------------+--------------------------------------+----------------------------+--------+
1 row in set (0.01 sec)
あと、 groups.status は実はBIT型なので↑では NULL でもないのに表示がおかしいとか
mysql> desc groups;
+----------------+--------------+------+-----+---------+-------+
| Field          | Type         | Null | Key | Default | Extra |
+----------------+--------------+------+-----+---------+-------+
| group_id       | varchar(64)  | NO   | PRI | NULL    |       |
| description    | varchar(256) | YES  |     | NULL    |       |
| master_uuid    | varchar(40)  | YES  | MUL | NULL    |       |
| master_defined | timestamp(6) | YES  |     | NULL    |       |
| status         | bit(1)       | NO   |     | NULL    |       |
+----------------+--------------+------+-----+---------+-------+
5 rows in set (0.00 sec)

mysql> SELECT CAST(status AS signed) FROM groups;
+------------------------+
| CAST(status AS signed) |
+------------------------+
|                      1 |
+------------------------+
1 row in set (0.00 sec)
暗黙のキャストに期待して UPDATE groups SET status = '0' とかやると
mysql> UPDATE groups SET status = '0' WHERE group_id = 'myfabric';
ERROR 1406 (22001): Data too long for column 'status' at row 1

mysql> UPDATE groups SET status = 0 WHERE group_id = 'myfabric';
Query OK, 1 row affected (0.02 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> UPDATE groups SET status = b'1' WHERE group_id = 'myfabric';
Query OK, 1 row affected (0.02 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> UPDATE groups SET status = 0b0 WHERE group_id = 'myfabric';
Query OK, 1 row affected (0.02 sec)
Rows matched: 1  Changed: 1  Warnings: 0
「あーINTからBITのキャストは出来るけど文字列だと直接BITにキャストできないのかー」とか「そういえば b'010111' みたいな記法あったよね」とか知ることができて新鮮です。

あとはMySQL Routerさんが
MySQL Fabric support was removed.
サポートやめちゃったので、今後MySQL Router 2.1以降に追随していくためにはこっちにもパッチを当てないといけない。 ( そもそもコンフィグパーサーから fabric+cache:// のハンドルが削り取られているので、シンタックスエラーとして扱われる。かなしい)
あー、課題は山積みだわー、つらいわー、mikasafabricほんとつらいわー(棒)
明日は @atsuizoさん で 「SELECT文をタイムアウト強制終了させる「MAX_EXECUTION_TIME」使ってる?」です!

2017年12月4日月曜日

ConoHaの上でひたすらMySQLをビルドする簡単なお仕事

この記事は ConoHa Advent Calendar 2017 の4日目の記事です。
「ConoHaの上で」と銘打ってはいますが、俺の普段使いのLinux環境がConoHaだからというだけで、VirtualBoxだろうとEC2だろうとCentOS 7.4なら全部似たような結果になると思います
というわけでまずは吊るしのConoHaのVPSインスタンスを作ります。
最近のMySQLはビルドに結構メモリーを食うのでメモリーは1GBのものを選びました(512MBだと、途中でOOM Killerに殺されるかまたは永遠にビルドが終わらないと思います。5.7とそれ以降)
WEBからポチポチしてSSHでログインできたら、さっさとビルドを開始します。
まずは 5.0からいきましょう。
2017/12/04現在、サポートが継続されているMySQLは5.5, 5.6, 5.7の3系統です。
サポートが切れているMySQLのソースコードも MySQL :: MySQL Product Archives から手に入れることができます。
(が、ここでもMySQL Fabricは存在しません。 泣かない
というわけでMySQL 5.0系の最終バージョンである5.0.96を取ってきてさっくりビルドしてみます。

$ wget https://downloads.mysql.com/archives/get/file/mysql-5.0.96.tar.gz
$ tar xf mysql-5.0.96.tar.gz 
$ cd mysql-5.0.96/
$ ll
なんかパーミッションがスゴいことになっていて、時代のせいなのかパッケージングが出鱈目だったのか気になります。
MySQL 5.1とそれ以前のバージョンでは configure スクリプトを使って下準備をします。

$ ./configure --prefix=/opt/mysql/5.0.96
..
configure: error: in `/root/mysql-5.0.96':
configure: error: no acceptable C compiler found in $PATH
See `config.log' for more details.
あ、エラった。流石に gcc とかデフォで入っていない様子。
$ sudo yum install -y gcc
$ ./configure --prefix=/opt/mysql/5.0.96
..
checking for termcap functions library... configure: error: No curses/termcap library found
今度は別のエラー。cursesライブラリーがないって言われるけれどこれ ncurses-libs のことではなくて ncurses-devel のことなので注意(数年前にハマったなあ)
$ sudo yum install -y ncurses-devel
$ ./configure --prefix=/opt/mysql/5.0.96

..
Thank you for choosing MySQL!

Remember to check the platform specific part of the reference manual
for hints about installing MySQL on your platform.
Also have a look at the files in the Docs directory.
無事に終わったぽいので make
$ make
..
DEPDIR=.deps depmode=none /bin/sh ../depcomp \
g++ -DDEFAULT_BASEDIR=\"/opt/mysql/5.0.96\" -DMYSQL_DATADIR="\"/opt/mysql/5.0.96/var\"" -DDEFAULT_CHARSET_HOME="\"/opt/mysql/5.0.96\"" -DSHAREDIR="\"/opt/mysql/5.0.96/share/mysql\"" -DDEFAULT_HOME_ENV=MYSQL_HOME -DDEFAULT_GROUP_SUFFIX_ENV=MYSQL_GROUP_SUFFIX -DDEFAULT_SYSCONFDIR="\"/opt/mysql/5.0.96/etc\"" -DHAVE_CONFIG_H -I. -I../include -I../zlib -I../include -I../include -I.    -O -DDBUG_OFF    -fno-implicit-templates -fno-exceptions -fno-rtti -c -o my_new.o my_new.cc
../depcomp: line 571: exec: g++: not found
make[2]: *** [my_new.o] Error 127
make[2]: Leaving directory `/root/mysql-5.0.96/mysys'
make[1]: *** [all-recursive] Error 1
make[1]: Leaving directory `/root/mysql-5.0.96'
make: *** [all] Error 2
C++コンパイラーの g++ が要るって(´・ω・`) だったら configure の段階で言ってよ!
$ sudo yum install -y gcc-c++
$ ./configure --prefix=/opt/mysql/5.0.96

$ make
gcc-c++を入れたら再 configure して(しないと make が通らなかった)もっかい makemake が終わったら make install--prefix で指定したパスにファイルをコピー。
$ sudo make install
はい5.0終わり。次5.1。
MySQL 5.1は5.0とそんなに変わらないので、同じくアーカイブのページから5.1系の最終バージョンである5.1.73を…あれ?
「いや、そのりくつはおかしい」と言いたくなるけれども取り敢えずアーカイブページには5.1.72までしかない。 特に切羽詰まっている(?)わけでもないので、今回は5.1.72でビルドすることにしよう。
$ wget https://downloads.mysql.com/archives/get/file/mysql-5.1.72.tar.gz
$ tar xf mysql-5.1.72.tar.gz
$ cd mysql-5.1.72
$ ./configure --prefix=/opt/mysql/5.1.72
$ make
$ sudo make install
特に詰まることもなく(5.0から追加で何か入れることもなく)5.1も終了。はい次5.5。
ここから現在サポートされているバージョンになるので、最新のマイナーバージョンであれば MySQL :: Download MySQL Community Server から、それ以前のマイナーバージョンであればまたアーカイブから取ってくることになる。
ダウンロードページでは5.7が全面に押し出されているけれども、「Looking for previous GA versions?」とか書いてあるのを探すと5.5と5.6へのリンクがあるのでそれを選んでダウンロード。
$ wget https://dev.mysql.com/get/Downloads/MySQL-5.5/mysql-5.5.58.tar.gz
$ tar xf mysql-5.5.58.tar.gz 
$ cd mysql-5.5.58/
$ cmake -DCMAKE_INSTALL_PREFIX=/usopt/mysql/5.5.58
-bash: cmake: command not found
MySQL 5.5とそれ以降では cmake を使って下準備をする。入ってなかったのでインストール。
$ sudo yum install -y cmake
$ cmake -DCMAKE_INSTALL_PREFIX=/usopt/mysql/5.5.58
..
-- Configuring done
-- Generating done
-- Build files have been written to: /root/mysql-5.5.58

$ make
$ sudo make install
あれ、意外とさっくり通ってしまった。まあいいや、5.5終わったので5.6。
$ wget https://dev.mysql.com/get/Downloads/MySQL-5.6/mysql-5.6.38.tar.gz
$ tar xf mysql-5.6.38.tar.gz
$ cd mysql-5.6.38/
$ cmake -DCMAKE_INSTALL_PREFIX=/opt/mysql/5.6.38
$ make
$ sudo make install
このへんからビルドに時間がかかるようになってくる(´・ω・`)
が、特にエラーもなく終わったので次5.7。
MySQL 5.7から先は、同じようにダウンロードページからソースの.tar.gzを探すと「Compressed TAR Archive」と「Compressed TAR Archive, Includes Boost Headers」というのが見つかる。
これは MySQLは5.7.5からBoostを使うようになった からで、別で自分でBoostを入れているような人はBoost同梱版を選ぶ必要は特にない(けれど、バージョン決め打ちでBoostを欲しがるようなので、同梱版の方が絶対に楽だと思う)
$ wget https://dev.mysql.com/get/Downloads/MySQL-5.7/mysql-boost-5.7.20.tar.gz
$ tar xf mysql-boost-5.7.20.tar.gz
$ cd mysql-5.7.20
$ cmake -DCMAKE_INSTALL_PREFIX=/opt/mysql/5.7.20 -DWITH_BOOST=./boost
$ make
$ sudo make install
さて、5.7も案外何事もなく終わったので開発中の最新版8.0に行きましょう。
MySQL 8.0(2017/12/04現在、8.0.3-rc)も MySQL :: Download MySQL Community Server のページの中で「Development Releases」を探すと出てきます。
$ wget https://dev.mysql.com/get/Downloads/MySQL-8.0/mysql-boost-8.0.3-rc.tar.gz
$ tar xf mysql-boost-8.0.3-rc.tar.gz
$ cd mysql-8.0.3-rc
$ cmake -DCMAKE_INSTALL_PREFIX=/opt/mysql/8.0.3 -DWITH_BOOST=./boost
$ make
$ sudo make install
:(;゙゚’ω゚’): あらフツーに通った…
もうちょっとなんかライブラリー要求されるかと思いましたが、意外とオプションなしなら通るものですね。
ちなみに、
$ du -sh /opt/mysql/*
66M     /opt/mysql/5.0.96
228M    /opt/mysql/5.1.72
342M    /opt/mysql/5.5.58
998M    /opt/mysql/5.6.38
1.9G    /opt/mysql/5.7.20
1.5G    /opt/mysql/8.0.3
5シリーズビルドするのに大体6時間くらいでした!
それでは楽しい MySQL on ConoHa ライフを!




2017年12月3日日曜日

ytkit - Yoku-san no ToolKITの紹介

このエントリーは OSS紹介 Advent Calendar 2017 の3日目の記事です。
ytkit はMySQLの運用に使いそうなちょっとしたスクリプト群です。
2017/12/03現在、ytkitには2つのスクリプトが存在しています。

yt-binlog-groupby

mysqlbinlog の出力結果をパイプで受け取って、テーブルや時間単位でGROUP BYするためのスクリプトです。
前身は mysqlbinlog_lister.pl というスクリプトで、これをテスタブルに書き直して機能を追加したものが yt-binlog-groupby になります。
↑2年前のブログ記事から使い方は特に変わっていないですが、「バイナリーログから更新のホットスポットを探す」ために使います。
最近、 --verboseexec_time の中央値と最大値を出す機能を追加しました。スレーブ側のバイナリーログ( log_slave_updates が指定されている時)では exec_time が「マスターで実行された時刻」と「スレーブで実行された時刻」の差になるので、スレーブ遅延の時などにどの辺に問題があったのかを切り分けるのに使えるんじゃないかと期待しています。
$ mysqlbinlog -vv /path/to/binary-log | yt-binlog-groupby --cell=10m --group-by=time,table --verbose
binlog entries between 171018 14:40 and 171130 19:30
171018 14:40    healthcheckdb.test3     13071   mid:0   max:1
171020 19:00    d1.t1   1       mid:0   max:0
171020 19:20    mysqlslap.t1    3976    mid:0   max:1
171020 19:30    d1.t1   1       mid:0   max:0
171023 14:10    d1.t2   1       mid:0   max:0
171023 14:10    d1.t3   1       mid:0   max:0
171025 19:20    d1.t1   1       mid:0   max:0
171116 18:00    mysql.time_zone 1       mid:0   max:0
171117 20:20    d1.t2   2       mid:0   max:0
171117 20:20    d1.t3   2       mid:0   max:0
171121 19:10    d1.t1   1       mid:0   max:0
171124 17:00    d1.t1   1       mid:0   max:0
171128 12:40    test.t1 16      mid:0   max:0

yt-healthcheck

Nagiosの check_mysql を置き換えるために書いたスクリプトです。
MySQLを割と一人で300台管理する技術 の時に紹介した監視テクニックが実装されています(あ、生ログをダンプする部分移植してないことに気付いた…)
「パラメーターのチューニングをしなくてもある程度動く」「マスターとスレーブを入れ替えてもパラメーターを変更する必要はない」「案外忘れそうなものをワーニングで拾う」あたりを念頭に作られています。
$ yt-healthcheck -h 127.0.0.1 -P 5638 -umsandbox -pmsandbox
WARNING on slave: read_only should be ON but current setting is OFF
どちらも日々のMySQL運用で「ちょっとほしい」ものをまとめているので、どんな環境に持って行ってもある程度は動く気がします。 MySQLと仲良くなるためにご利用いただければ幸いです。
明日は @fujiwaraさん の「alp と Plack::Middleware::QueryCounter を合わせて使う話を書きます」 です!

2017年12月1日金曜日

これが多分最後の「MySQL Fabricつらい」

やあ (´・ω・`)
ようこそ、MySQL Fabricのお墓へ。
このサキーラはサービスだから、まず飲んで落ち着いて欲しい。
うん、「やっぱり」なんだ。済まない。
地獄の沙汰もって言うしね、謝って許してもらおうとも思っていない。
でも、このスレタイを見たとき、君は、きっと言葉では言い表せない
「めきめき」みたいなものを感じてくれたと思う。
 殺伐とした世の中で、そういう気持ちを忘れないで欲しい
そう思って、この記事を書いたんだ。
じゃあ、注文を聞こうか。

さて本題。
この記事は MySQL Casual Advent Calendar 2017 の1日目の記事です。
全国1.000000人のユーザー(俺調べ)に 愛された MySQL Fabricが、2017/07 ついに(?)、 EOLになりました
つらい(´;ω;`)

MySQL Fabricが何 だったか については、2014年に書かれた 高可用性とデータ・シャーディングを実現できるMySQL Fabricとは? | Think IT(シンクイット) が詳しい。
自動フェイルオーバーによる高可用性と、参照/更新処理に対する負荷分散による拡張性を実現できます。そしてこれらをアプリケーションから意識することなく実現できる、という大きな利点があります。MySQL Fabricを使うと、MySQLサーバーの構成が変わってもアプリケーションからの接続先を変更する必要がありません。
事実、MySQL Fabric + MySQL Routerの構成でマスターのスイッチオーバーやフェイルオーバーはできるし、スレーブを追加しても mysqlfabric group add .. するだけで、アプリケーションはローカルに積み込んだ mysqlrouter に対して接続すればいいだけでコンフィグをいじる必要はない。
とても便利だ。 とても便利なはずだった。
でも流行らなかったので マイエスキューエル先生の次回作 にご期待くださいになってしまった。
 理由はいくつか想像していて、 2015年時点 でこんなことを言っていたようだ。
  1. Fabric対応コネクターが必要
  2. MySQL 5.6以上かつGTIDが必要
  3. バッキングストア(mysqlfabricデーモンが情報をストアするためのmysqld)を自前で冗長化しないといけない
  4. ググったら 「MySQL Fabric つらい」 とかサジェストされる
正直すまんかった。

MySQL Fabricはなんと GitHubのリポジトリーを削除される という憂き目に遭って、わずかに Launchpad にその姿をとどめるのみになっている。
MySQL Proxyのリポジトリーすら残ってるのに。
そこまで黒歴史扱いしなくても。。
俺が
夢を見ていて、そもそもリポジトリーなんて存在しなかったんじゃ? とか思ったけれど、バグレポートの中でURLを示しているものがあったので墓標の代わりにメモしておく。

が、しかしまあもう本番に組み込んじゃってるので、MySQL Fabricは死んでも mikasafabric for MySQL は生き続けるんじゃよ。
そしてmikasafabricに関する愚痴は MySQL Casual Advent Calendar 2017 の8日目でするんじゃよ。
それではバイバイ、MySQL Fabric。

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';
..
どうでしょう。

2017年11月6日月曜日

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 さんでした。ごちそうさまでした。