Quantcast
Channel: 日々の覚書
Viewing all 589 articles
Browse latest View live

CREATE USER .. DEFAULT ROLE .. で指定すると一発でROLEも許可される

$
0
0

TL;DR

mysql> CREATE USER hoge@xxx.xxx.xxx.xxx IDENTIFIED BY 'password';
mysql> GRANT hoge_role TO hoge@xxx.xxx.xxx.xxx;
mysql> ALTER USER hoge@xxx.xxx.xxx.xxx DEFAULT ROLE hoge_role;
mysql> CREATE USER hoge@xxx.xxx.xxx.xxx IDENTIFIED BY 'password' DEFAULT ROLE hoge_role;
は同じ状態になる。
当たり前といえば当たり前なんだけど、ちょっと感動したのでメモ。

MySQL 8.0で追加されたROLEの話は↓の記事。
ロールを作ってから
  1. ユーザー作る
  2. ロールを許可する
  3. デフォルトロール設定する
    でやらないといけないのかなと思ってたら一発で指定できた。

5.7とそれ以前

mysql> CREATE USER hoge@xxx.xxx.xxx.xxx IDENTIFIED BY 'password';
mysql> GRANT ALL ON hogedb.* TO hoge@xxx.xxx.xxx.xxx;
mysql> GRANT ALL ON fugadb.* TO hoge@xxx.xxx.xxx.xxx;

mysql> CREATE USER hoge@xxx.xxx.xxx.yyy IDENTIFIED BY 'password';
mysql> GRANT ALL ON hogedb.* TO hoge@xxx.xxx.xxx.yyy;
mysql> GRANT ALL ON fugadb.* TO hoge@xxx.xxx.xxx.yyy;
  • APサーバーが増えるたびに CREATE USER + GRANT * スキーマ数がめんどい
    • スキーマが増減した時とか死にたくなる
      • そして mysql.dbの直接変更に手を出す…
  • 「おとなしく hoge@xxx.xxx.%にしたらいいのでは?」「それな」

8.0

mysql> CREATE ROLE hoge_role;
mysql> GRANT ALL ON hogedb.* TO hoge_role;
mysql> GRANT ALL ON fugadb.* TO hoge_role;

mysql> CREATE USER hoge@xxx.xxx.xxx.xxx IDENTIFIED BY 'password' DEFAULT ROLE hoge_role;
mysql> CREATE USER hoge@xxx.xxx.xxx.yyy IDENTIFIED BY 'password' DEFAULT ROLE hoge_role;
  • 楽だ…

MySQL 8.0でLOAD DATA LOCAL INFILEが "ERROR 1148 (42000): The used command is not allowed with this MySQL version"で失敗する時

$
0
0

TL;DR


吊るしのMySQL 8.0で mysqlコマンドラインクライアントから LOAD DATA LOCAL INFILEを実行すると転けます。
mysql80 125> LOAD DATA LOCAL INFILE '/tmp/aaa' INTO TABLE t1;
ERROR 1148 (42000): The used command is not allowed with this MySQL version
LODA DATA LOCAL INFILEを実行するには2つの条件が必要で、
  1. LOAD DATA LOCAL INFILEを実行するコネクションに CLIENT_LOCAL_FILESケーパビリティー(オプションだと思って)が設定されていること
  2. サーバー側で opt_local_infileが設定されていること
2.mysqldlocal_infileオプションなのでわかりやすい。単にデフォルトが5.7とそれ以前の “1” から8.0では “0” に変わったというだけ。
再起動しなくても SET GLOBALSET PERSISTで設定できる。
mysql80 125> SELECT @@local_infile;
+----------------+
| @@local_infile |
+----------------+
| 0 |
+----------------+
1 row in set (0.00 sec)

mysql80 125> SET PERSIST local_infile= 1;
Query OK, 0 rows affected (0.00 sec)

mysql80 125> SELECT @@local_infile;
+----------------+
| @@local_infile |
+----------------+
| 1 |
+----------------+
1 row in set (0.00 sec)
サーバー側( 2.)だけ満たされていても、コネクションに CLIENT_LOCAL_FILESケーパビリティー( 1.)はついてないのでやっぱり転ける。
同じエラーなので見分けにくい。
mysql80 125> LOAD DATA LOCAL INFILE '/tmp/aaa' INTO TABLE t1;
ERROR 1148 (42000): The used command is not allowed with this MySQL version
1.が満たされているかどうかをgdbを使わずに確かめる方法が見当たらなかったのだけれど、 mysqlコマンドラインクライアントであれば —local-infileオプションを有効にするとこのケーパビリティーのフラグが立つ。ただし接続しながら変えることはできないのでこっちは切断してから再接続する。
$ mysql80 --local-infile=1
mysql80> use d1
mysql80 132> LOAD DATA LOCAL INFILE '/tmp/md5' INTO TABLE t1;
サーバーサイドのlocal_infileとクライアントサイドのlocal_infileがそれぞれ別で、それぞれ暗黙のデフォルトが0になったから両方で指定しないといけないよ、というお話でした
ちなみにConnector/Cなら mysql_real_connectを呼ぶときに client_flagCLIENT_LOCAL_FILESを立てるか、 mysql_optionsMYSQL_OPT_LOCAL_INFILEを有効にしてやればおk。

innodb_ft_enable_stopword が無効にできなかったはなし

$
0
0

TL;DR

  • innodb_ft_enable_stopwordは2018/08/22現在ドキュメントの記載が “GLOBAL” のみになっているけど、実際は “GLOBAL, SESSION” で実効値はセッション側
  • このパラメーターでストップワードを判定させるか否かは CREATE TABLEまたは ALTER TABLEした時点の session.innodb_ft_enable_stopwordに依存する

元ネタはMySQL CasualのSlackでのこの発言。
(文中の引用元リンクがたどれない方は是非とも MySQL CasualのSlackへ!
innodb_ft_enable_stopwordパラメータですが、set globalでON/OFFが切り替えられると思っていたのですが、
実際に試してみたところOFFになりません。
(コマンド自体はエラーにならない)
マニュアルには Global って書いてあるんですけど、set コマンドでは Global と Session の両方あるような雰囲気ですね…。
むむむと思って調べてみた。
まず、 innodb_ft_enable_stopwordMYSQL_THDVAR_BOOLで定義されているので、グローバルオンリーではなくてグローバルとセッションの両方持っているのはそれで合っていそう(グローバルオンリーだと MYSQL_SYSVAR_*型)
この値(ft_enable_stopword)を唯一引きまわしているinnobase_fts_load_stopwordの中でも、 THDVARで呼ばれているので、グローバルとセッションと両方ある時はセッション側の値が実効値、という定石の通りになっていると思われる。
innobase_fts_load_stopwordが呼ばれるのはcreate_table_info_t::create_table_update_dictとprepare_inplace_alter_table_dictの中。
というところで、ドキュメントに以下の記述を見つけた。
Specifies that a set of stopwords is associated with an InnoDB FULLTEXT index at the time the index is created.
なるほど、フルテキストインデックスが生成される時に決まるのね。

じゃあ実験。
ストップワード関連のオプションをデフォルトのままで CREATE TABLE, INSERT INTOすると、フルテキストインデックスの中味に “one” を “on”, “ne” に分割したもののうちストップワードの “on” を除いた “ne” だけが記録される。
mysql57 2> SELECT @@session.innodb_ft_enable_stopword, @@global.innodb_ft_enable_stopword;
+-------------------------------------+------------------------------------+
| @@session.innodb_ft_enable_stopword | @@global.innodb_ft_enable_stopword |
+-------------------------------------+------------------------------------+
| 1 | 1 |
+-------------------------------------+------------------------------------+
1 row in set (0.02 sec)

mysql57 2> create table t1 (val varchar(32), fulltext key (val) with parser ngram);
Query OK, 0 rows affected (0.15 sec)

mysql57 2> INSERT INTO t1 VALUES ('one');
Query OK, 1 row affected (0.23 sec)

mysql57 2> SET GLOBAL innodb_ft_aux_table = 'd1/t1';
Query OK, 0 rows affected (1.59 sec)

mysql57 2> SELECT * FROM i_s.innodb_ft_index_cache;
+------+--------------+-------------+-----------+--------+----------+
| WORD | FIRST_DOC_ID | LAST_DOC_ID | DOC_COUNT | DOC_ID | POSITION |
+------+--------------+-------------+-----------+--------+----------+
| ne | 2 | 2 | 1 | 2 | 1 |
+------+--------------+-------------+-----------+--------+----------+
1 row in set (0.01 sec)
次、まず SET SESSION innodb_ft_enable_stopword= 0でセッション側の設定をOFFにしてもう一度 INSERTしてみる
mysql57 2> SET SESSION innodb_ft_enable_stopword= 0;
Query OK, 0 rows affected (0.00 sec)

mysql57 2> SELECT @@session.innodb_ft_enable_stopword, @@global.innodb_ft_enable_stopword;
+-------------------------------------+------------------------------------+
| @@session.innodb_ft_enable_stopword | @@global.innodb_ft_enable_stopword |
+-------------------------------------+------------------------------------+
| 0 | 1 |
+-------------------------------------+------------------------------------+
1 row in set (0.00 sec)

mysql57 2> INSERT INTO t1 VALUES ('one');
Query OK, 1 row affected (0.01 sec)

mysql57 2> SELECT * FROM i_s.innodb_ft_index_cache;
+------+--------------+-------------+-----------+--------+----------+
| WORD | FIRST_DOC_ID | LAST_DOC_ID | DOC_COUNT | DOC_ID | POSITION |
+------+--------------+-------------+-----------+--------+----------+
| ne | 2 | 3 | 2 | 2 | 1 |
| ne | 2 | 3 | 2 | 3 | 1 |
+------+--------------+-------------+-----------+--------+----------+
2 rows in set (0.00 sec)
セッション値を変えても INSERTした時にストップワードの “on” が取り除かれている。
じゃあグローバル値はどうかな。
mysql57 2> SET GLOBAL innodb_ft_enable_stopword= 0;
Query OK, 0 rows affected (0.00 sec)

mysql57 2> SELECT @@session.innodb_ft_enable_stopword, @@global.innodb_ft_enable_stopword;
+-------------------------------------+------------------------------------+
| @@session.innodb_ft_enable_stopword | @@global.innodb_ft_enable_stopword |
+-------------------------------------+------------------------------------+
| 0 | 0 |
+-------------------------------------+------------------------------------+
1 row in set (0.05 sec)

mysql57 2> INSERT INTO t1 VALUES ('one');
Query OK, 1 row affected (0.00 sec)

mysql57 2> SELECT * FROM i_s.innodb_ft_index_cache;
+------+--------------+-------------+-----------+--------+----------+
| WORD | FIRST_DOC_ID | LAST_DOC_ID | DOC_COUNT | DOC_ID | POSITION |
+------+--------------+-------------+-----------+--------+----------+
| ne | 2 | 4 | 3 | 2 | 1 |
| ne | 2 | 4 | 3 | 3 | 1 |
| ne | 2 | 4 | 3 | 4 | 1 |
+------+--------------+-------------+-----------+--------+----------+
3 rows in set (0.00 sec)
変わらない。フルテキストインデックスが生成されるタイミングで有効なのは本当らしい。
じゃあこの状態(グローバルOFF、セッションOFF)の状態でもういっこテーブルを作ってみる。
mysql57 2> CREATE TABLE t2 (val varchar(32), FULLTEXT KEY (val) WITH PARSER ngram);
Query OK, 0 rows affected (0.08 sec)

mysql57 2> INSERT INTO t2 VALUES ('one');
Query OK, 1 row affected (0.01 sec)

mysql57 2> SET GLOBAL innodb_ft_aux_table = 'd1/t2';
Query OK, 0 rows affected (0.01 sec)

mysql57 2> SELECT * FROM i_s.innodb_ft_index_cache;
+------+--------------+-------------+-----------+--------+----------+
| WORD | FIRST_DOC_ID | LAST_DOC_ID | DOC_COUNT | DOC_ID | POSITION |
+------+--------------+-------------+-----------+--------+----------+
| ne | 2 | 2 | 1 | 2 | 1 |
| on | 2 | 2 | 1 | 2 | 0 |
+------+--------------+-------------+-----------+--------+----------+
2 rows in set (0.00 sec)
ストップワードが判定されなくなって “on” もフルテキストインデックスの中に入ってくるようになった。
その後、セッション値とグローバル値とパターンを変えていくつか試してみると、
  • CREATE TABLEまたは ALTER TABLEの時点で指定していた innodb_ft_enable_stopwordのセッション値で判断される
    • innodb_optimize_fulltext_only= 1ALTER TABLEしてもその時点の値が反映される
  • INSERT, SELECTの時の innodb_ft_enable_stopwordは関係ない
    • SET SESSION innodb_ft_enable_stopwordだけで結果が変わったらどうしようかと思っちゃった
  • ということは単にドキュメントの間違いで、本当は “GLOBAL, SESSION” である
ごちそうさまでした!

【2018/08/22 17:22】
ばぐれぽしておいた!

MySQL Bugs: #92118: innodb_ft_enable_stopword is wrongly described as "GLOBAL" scope in Docs



『』

Club MySQL #3 ~ yoku0825 のつくりかた に登壇してきました(?)

$
0
0
去る8/20、Club MySQLの第3回「yoku0825のつくりかた」に登壇してきました。
Club MySQLなのに MySQLではなく ピンクのかわいいおとうふのことを語ってしまって恐縮です。
主催の 坂井さん、会場をお貸しいただいた サイボウズさん、ご来場いただいたみなさま、どうもありがとうございました。
当日のハッシュタグまとめました(ただよう自作自演風味)


まずは坂井さんから、 “Club MySQLは日本MySQLユーザ会の一形態であり、スピンアウトじゃないよ” とクギを刺されます :D
  • 日本MySQLユーザ会 の副代表、 @sakaik さんによるスピンアウト勉強会です
    • Club MySQL は、ひとりの講演者の話をじっくりと聞こう、という趣向の、日本MySQLユーザ会の新しいイベントシリーズです だそう
( ´-`).oO(ここに訂正してお詫び申し上げます…

俺とそーだいさんの馴れ初めは多分↓これで、


その後、中国地方DB勉強会 in 東京(何を言っているのかわからねーと思うが略)で仲良くなって一緒にヤパチーでキャッキャウフフしたりYAPC::Hokkaido逝ったりともう3~4年の付き合いになるようです。
物理そーだいさん(?)から何度か「僕 弟キャラなので色んなこと教えてもらえるんですよねー」(リアル弟なのかは忘れましたが)って聞いてて、なるほど確かにそーだいさん可愛がられキャラだし聞き方も上手いよな、とか思ってたんですが、スライドの「あじぇんだ」にガリっと「利用手順」「活用事例」とか紹介されてやっぱり上手いなと思いました。
そーだいさんのLTが思いのほか面白く、本当はその5分でちゃちゃっと追加しようと思っていた第4部が未完(ただし5分作業できたとしても完成していたかどうかは定かではない)のまま、「yoku0825のつくりかた」のセッションに入ります。

前日まで何を話すか全然決まらなくて書いちゃ破き書いちゃ破き状態で、上手くまとめられなかったような気がしたんですが、楽しんでいただけたなら何よりでした!
ありがとうございました!
最後になりますが、 SH2さん愛してますよ ;D

「max_allowed_packetの基本的な動き」がどうしてそうなるのかのはなし

$
0
0

TL;DR

  • 1SQLのサイズ上限は、max_allowed_packetとnet_buffer_lengthで制御される。
  • max_allowed_packetはグローバルにデフォルト値を持ち、コネクション確立時にセッションにコピーされ、セッション変数は読み込み専用である。(セッションに適用するにはconnectなど再接続が必要)
  • net_buffer_lengthはグローバルにデフォルト値を持ち、コネクション確立時にセッションにコピーされ、セッション変数も変更可能である。
  • net_buffer_length => max_allowed_packetの場合、net_buffer_lengthが1SQLの上限値になる。
  • net_buffer_length < max_allowed_packetの場合、max_allowed_packetが1SQLの上限値になる。
とまとめられているが、これは

ということ。

まず、 max_allowed_packetの上限に引っかかった時に出るエラーである、 “Got a packet bigger than ‘max_allowed_packet’ bytes” からたどっていくことにする。
(See also Dive into MySQL Error - Speaker Deck
ソースコードは5.7.23のものにしていますよん。
$ grep "max_allowed_packet" include/mysqld_ername.h
{ "ER_NET_PACKET_TOO_LARGE", 1153, "Got a packet bigger than \'max_allowed_packet\' bytes" },
{ "ER_TOO_LONG_STRING", 1162, "Result string is longer than \'max_allowed_packet\' bytes" },
{ "ER_WARN_ALLOWED_PACKET_OVERFLOWED", 1301, "Result of %s() was larger than max_allowed_packet (%ld) - truncated" },
MySQLへのJDBC接続で、とあるバグを踏むまでの話 -(1)「max_allowed_packet」の基本的な働き - なからなLifeではバルクインサートだったので、 ER_NET_PACKET_TOO_LARGEの方だと思う。
$ grep -r ER_NET_PACKET_TOO_LARGE . | grep -v mysql-test
./include/sql_state.h:{ ER_NET_PACKET_TOO_LARGE ,"08S01", "" },
./include/mysqld_ername.h:{ "ER_NET_PACKET_TOO_LARGE", 1153, "Got a packet bigger than \'max_allowed_packet\' bytes" },
./include/mysqld_error.h:#define ER_NET_PACKET_TOO_LARGE 1153
./libmysql/libmysql.c: else if (net->last_errno == ER_NET_PACKET_TOO_LARGE)
./sql-common/client.c: set_mysql_error(mysql, net->last_errno == ER_NET_PACKET_TOO_LARGE ?
./sql-common/client.c: if (net->last_errno == ER_NET_PACKET_TOO_LARGE)
./sql/net_serv.cc: net->last_errno= ER_NET_PACKET_TOO_LARGE;
./sql/net_serv.cc: my_error(ER_NET_PACKET_TOO_LARGE, MYF(0));
./sql/rpl_rli_pdb.cc: with error ER_NET_PACKET_TOO_LARGE.
./sql/rpl_slave.cc: mi->report(ERROR_LEVEL, ER_NET_PACKET_TOO_LARGE,
./sql/share/errmsg-utf8.txt:ER_NET_PACKET_TOO_LARGE 08S01
./includeのやつは定義系だし、 ./libmysql, ./sql-commonのやつは ==で比較しているのでこのエラーを受け取った後にどうするかの処理だろう。
./sql/rpl_*はレプリケーション関連なので取り敢えずパスして、 ./sql/net_serv.ccの中で代入しているからここが本丸のような気がする。
grepで引っかかった2行は net_reallocという関数の中に入っていて、名前から察するにここが
パケットメッセージバッファーは net_buffer_length バイトに初期化されますが、必要に応じて max_allowed_packet バイトまで大きくできます。
のパケットメッセージバッファーを大きくする処理なのであろう。
コイツが呼ばれているところを探しに行く。
$ grep -r net_realloc .
./include/mysql.h.pp:my_bool net_realloc(NET *net, size_t length);
./include/mysql_com.h:my_bool net_realloc(NET *net, size_t length);
./libmysql/libmysql.c: res= net_realloc(net, buf_length + length);
./sql/net_serv.cc:my_bool net_realloc(NET *net, size_t length)
./sql/net_serv.cc: DBUG_ENTER("net_realloc");
./sql/net_serv.cc: must match the size of the buffer allocated in net_realloc().
./sql/net_serv.cc: if ((pkt_data_len >= net->max_packet) && net_realloc(net, pkt_data_len))
./libmysqlとはlibmysqlclient.so(Connector/C)のコードなので外すとするとまだ同じ ./sql/net_serv.ccの行。開いてみると net_read_packetの中で、「今のパケットメッセージバッファーよりもデータが大きければ net_realloc」な記述に当たる。
というわけで、
  • net_buffer_length => max_allowed_packetの場合、net_buffer_lengthが1SQLの上限値になる。
の理由は、 net_buffer_lengthのサイズでアロケートされたパケットメッセージバッファが足りなくなって拡張しようとした時に初めて max_allowed_packetと比較されてエラーに分岐するから、でした。
Cこわくないよ!
(あっdbts2018のブログがまだ下書きn

db tech showcase Tokyo 2018に参加してきました

$
0
0
2018/09/19~21の db tech showcase Tokyo 2018 | db tech showcase(このサイト、URLに年を示すものが何もないので来年になると上書きされちゃうんですよね) に参加してきました。
タイトルは「Dive into MySQL Error」、その名の通り、MySQLのエラーのちょっと深いところを覗いていくセッションです。
当日朝イチで雨にも関わらず足を運んでくださったみなさま、スライドをご覧になっていただいたみなさま、
3日目朝イチのセッションを1日目の懇親会終了後に即決してくださったインサイトテクノロジーの石川さん、松尾さん、
本当にありがとうございました。




1日目の懇親会が終わるまでは完全にオーディエンスの予定だったので資料は何も用意しておらず、取り敢えず @mita2さんと @keny_lalaさんの発表だけ聞ければいいかな、と思いながら、「MySQL UDFとGo言語で作るビッグデータ前処理基盤」の話が気になって聞いていたりしました。

MySQL at Yahoo! JAPAN

  • 規模すごい…(小並感)
  • 「サイズが小さいのがたくさん増えていく」
  • Percona XtraDB Cluster

LINEのMySQL運用について

  • MySQLとRedis
  • 通称mondb+
  • HA用のスクリプトを自分でメンテするとつらいけど、ブラックボックスより安心しませんか? :-P

日本発!MySQL UDFとGo言語で作るビッグデータ前処理基盤

  • Goで作った超すごい住所名寄せライブラリーをMySQLのUDFから叩く
  • MySQLのUDFがどうこうじゃなくてGoで作ったライブラリーがすごい
  • 質疑時間で MySQLでUDF作ってる人が「わたしはこうやってますよ」って言ってたのが面白かった
  • 繰り返しになるけれどMySQLどうでもいい、Goで作ったライブラリーが超すごい
    • ミーカンパニーさんもちゃんと認識していたw

1日目のセッションを聞いて、懇親会で ミーカンパニーさんGoのライブラリーをすごいすごい言って、いい感じに酔っぱらったところで3日目の登壇を快諾していただき(その場にいた @tadayama_jpさんが「俺の時は怒られたのに…」って仰ってて超笑った(そこに立ってらしたのを認識してなかったので))、半日でスライドを仕上げて金曜日に至りました。
今年はなんか忙しさにかまけて、一日中いる日も無かったし飲んだのも初日だけでしたが、セッションやらせてもらってすごく楽しうございました。
どうもありがとうございました。

MySQL Router 8.0.12に同梱されているhttp_serverプラグインの謎について

$
0
0

TL;DR


もちろん MySQL :: MySQL Router 8.0を全文検索しても “http” で引っかかってこない。
コミットを見るとMySQL Router 8.0.12から入ったっぽいけれど、もちろん MySQL :: MySQL Router Release Notes :: Changes in MySQL Router 8.0.12 (2018-07-27, General Availability)にも何も書いてない。
流石に “Linux Generic” やrpmパッケージには入ってなかったけど、特にcmakeにオプションを渡すでもなく、ソースビルドすると何の断りもなく勝手に http_server.soというプラグインが出来上がる。
$ cd mysql-router-8.0.12
$ cmake .
$ make
$ ll stage/lib/mysqlrouter/http*
-rwxrwxr-x 1 yoku0825 yoku0825 494096 Oct 2 14:59 stage/lib/mysqlrouter/http_server.so
名前から察するに、全国 1.000000人のファンを擁するMySQL HTTP Plugin的な感じなのかしらん。MySQL RouterがHTTPをしゃべって、裏ではMySQLプロトコルでmysqldとやり取りしてくれる…的な。
(MySQL HTTP Pluginについてはこのへん… 日々の覚書: MySQLジャンキーにngx_mrubyを与えた結果
http_server_plugin.ccを見る限り、 [http_serevr]セクションを見つけると立ち上がるらしい。
デフォルトは 0.0.0.0:5555バインドされるようで、 static_folderというパラメーターもあるようだ。
…他は?;
取り敢えず試してみる。
plugin_folderをビルドしたパスに合わせるのと、最後に “[http_server]” とセクションだけ定義してやる。
$ cp packaging/rpm-oel/mysqlrouter.conf ./mysqlrouter.conf
$ vim ./mysqlrouter.conf
..
plugin_folder = /home/yoku0825/mysql-router-8.0.12/stage/lib/mysqlrouter
..
[http_server]

$ sudo ./stage/bin/mysqlrouter --config=./mysqlrouter.conf

$ sudo less /var/log/mysqlrouter/mysqlrouter.log

2018-10-02 16:15:52 main INFO [7f250a835780] Loading all plugins.
2018-10-02 16:15:52 main INFO [7f250a835780] plugin 'http_server:' loading
2018-10-02 16:15:52 main INFO [7f250a835780] plugin 'keepalive:' loading
2018-10-02 16:15:52 main INFO [7f250a835780] Initializing all plugins.
2018-10-02 16:15:52 main INFO [7f250a835780] plugin 'http_server' initializing
2018-10-02 16:15:52 http_server INFO [7f250a835780] listening on 0.0.0.0:5555
2018-10-02 16:15:52 main INFO [7f250a835780] plugin 'keepalive' initializing
2018-10-02 16:15:52 main INFO [7f250a835780] Starting all plugins.
2018-10-02 16:15:52 main INFO [7f2507a67700] plugin 'http_server:' starting
2018-10-02 16:15:52 main INFO [7f2507266700] plugin 'keepalive:' starting
2018-10-02 16:15:52 keepalive INFO [7f2507266700] keepalive started with interval 60
2018-10-02 16:15:52 keepalive INFO [7f2507266700] keepalive
2018-10-02 16:15:52 main INFO [7f250a835780] Running.

$ curl localhost:5555
<HTML><HEAD>
<TITLE>404 Not Found</TITLE>
</HEAD><BODY>
<H1>Not Found</H1>
</BODY></HTML>
…しゃべった………。
static_folderを設定してもっかい起動してみる。
$ vim ./mysqlrouter.conf
..
[http_server]
static_folder= /home/yoku0825/mysql-router-8.0.12

$ sudo ./stage/bin/mysqlrouter --config=./mysqlrouter.conf

$ curl localhost:5555/src/http/src/http_server_plugin.cc
..
ファー、見えた…。
これ、いつか static_folder以外にもURIとSQLのマッピングとかできるようになってmyhttpプラグイン再び、になるのかしらん。
期待age(?)

mysqlrouter.logの "keepalive"が気になる人へ

$
0
0

TL;DR

  • アレは単に一定間隔でログを吐いているだけ、ログ監視でもしない限り意味はない
  • 実は “[keepalive]” セクションに “interval” と “runs” が設定できる
  • もちろんマニュアルには何も書いてない

初めてMySQL Routerを使い始めたころから、ずっと「なんだろうこれ」「どことkeepaliveしてんねん」と思っていたこのログ。
$ sudo less /var/log/mysqlrouter/mysqlrouter.log
..
2018-04-03 16:33:00 INFO [7fe222415700] keepalive
2018-04-03 16:34:00 INFO [7fe222415700] keepalive
2018-04-03 16:35:00 INFO [7fe222415700] keepalive
実はどことも通信なんてしていない。
ソースコードはここ。
mysql-router/keepalive.cc at 8.0.12 · mysql/mysql-router
120行もないコードの中でしていることといえば、(最終的には)wait_forで “interval * 1000” ミリ秒 スリープするのを “runs” で指定された回数(0の場合は無限に)繰り返すだけ。
確かに言われてみればコンフィグファイルに書いてあったけど…なんか腑に落ちない…。。
ちなみにこの “runs” 、満了すると黙って keepalive プラグインだけが沈黙する。なんか言えよ…。(ちなみにkeepalive以外のプラグインが有効でない場合は全プラグインがアンロードされてプロセスが正常終了する)
$ tail -f /var/log/mysqlrouter/mysqlrouter.log
2018-10-03 12:30:38 main INFO [7fd33d238780] Loading all plugins.
2018-10-03 12:30:38 main INFO [7fd33d238780] plugin 'keepalive:' loading
2018-10-03 12:30:38 main INFO [7fd33d238780] plugin 'routing:master' loading
2018-10-03 12:30:38 main INFO [7fd33d238780] Initializing all plugins.
2018-10-03 12:30:38 main INFO [7fd33d238780] plugin 'keepalive' initializing
2018-10-03 12:30:38 main INFO [7fd33d238780] plugin 'routing' initializing
2018-10-03 12:30:38 main INFO [7fd33d238780] Starting all plugins.
2018-10-03 12:30:38 main INFO [7fd339fec700] plugin 'keepalive:' starting
2018-10-03 12:30:38 keepalive INFO [7fd339fec700] keepalive started with interval 6
2018-10-03 12:30:38 keepalive INFO [7fd339fec700] keepalive will run 3 time(s)
2018-10-03 12:30:38 keepalive INFO [7fd339fec700] keepalive
2018-10-03 12:30:38 main INFO [7fd3397eb700] plugin 'routing:master' starting
2018-10-03 12:30:38 main INFO [7fd33d238780] Running.
2018-10-03 12:30:38 routing INFO [7fd3397eb700] [routing:master] started: listening on 127.0.0.1:13306
2018-10-03 12:30:44 keepalive INFO [7fd339fec700] keepalive
2018-10-03 12:30:50 keepalive INFO [7fd339fec700] keepalive
取り敢えず3年来の謎は解けたので良いとしよう。。

MySQL 8.0ではカラムのリネームに ALTER TABLE RENAME COLUMN 構文が使える

$
0
0

TL;DR


今まではこう
mysql57 40> CREATE TABLE t1 (num int unsigned not null, val varchar(32) not null, PRIMARY KEY(num), FULLTEXT KEY (val) WITH PARSER ngram);
Query OK, 0 rows affected (0.06 sec)

mysql57 40> SHOW CREATE TABLE t1\G
*************************** 1. row ***************************
Table: t1
Create Table: CREATE TABLE `t1` (
`num` int(10) unsigned NOT NULL,
`val` varchar(32) NOT NULL,
PRIMARY KEY (`num`),
FULLTEXT KEY `val` (`val`) /*!50100 WITH PARSER `ngram` */
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)

mysql57 40> ALTER TABLE t1 CHANGE val abc varchar(32) NOT NULL;
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0

mysql57 40> SHOW CREATE TABLE t1\G
*************************** 1. row ***************************
Table: t1
Create Table: CREATE TABLE `t1` (
`num` int(10) unsigned NOT NULL,
`abc` varchar(32) NOT NULL,
PRIMARY KEY (`num`),
FULLTEXT KEY `val` (`abc`) /*!50100 WITH PARSER `ngram` */
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
1 row in set (0.01 sec)
CHANGEの後にデータ型もつけなければいけなかったのが面倒だった。 NOT NULLをつけるのを忘れて死にかけたりした人も世の中には存在するんじゃなかろうか。
これがMySQL 8.0ではこうじゃ。
mysql80 24> CREATE TABLE t1 (num int unsigned not null, val varchar(32) not null, PRIMARY KEY(num), FULLTEXT KEY (val) WITH PARSER ngram);
Query OK, 0 rows affected (0.15 sec)

mysql80 24> SHOW CREATE TABLE t1\G
*************************** 1. row ***************************
Table: t1
Create Table: CREATE TABLE `t1` (
`num` int(10) unsigned NOT NULL,
`val` varchar(32) COLLATE utf8mb4_ja_0900_as_cs NOT NULL,
PRIMARY KEY (`num`),
FULLTEXT KEY `val` (`val`) /*!50100 WITH PARSER `ngram` */
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_ja_0900_as_cs
1 row in set (0.00 sec)

mysql80 24> ALTER TABLE t1 RENAME COLUMN val TO abc;
Query OK, 0 rows affected (0.04 sec)
Records: 0 Duplicates: 0 Warnings: 0

mysql80 24> SHOW CREATE TABLE t1\G
*************************** 1. row ***************************
Table: t1
Create Table: CREATE TABLE `t1` (
`num` int(10) unsigned NOT NULL,
`abc` varchar(32) COLLATE utf8mb4_ja_0900_as_cs NOT NULL,
PRIMARY KEY (`num`),
FULLTEXT KEY `val` (`abc`) /*!50100 WITH PARSER `ngram` */
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_ja_0900_as_cs
1 row in set (0.00 sec)
MySQL 5.7で追加されたALTER TABLE .. RENAME INDEXが好評だったんですかね!
ただし、generated columnがあると古い方のカラムを参照し続けるため ALTER TABLEが転ける。これは CHANGEでも転けるんだけれども。
mysql80 24> ALTER TABLE t1 ADD v_abc varchar(32) AS (MD5(abc)) NOT NULL;
Query OK, 0 rows affected (0.05 sec)
Records: 0 Duplicates: 0 Warnings: 0

mysql80 24> SHOW CREATE TABLE t1\G
*************************** 1. row ***************************
Table: t1
Create Table: CREATE TABLE `t1` (
`num` int(10) unsigned NOT NULL,
`abc` varchar(32) COLLATE utf8mb4_ja_0900_as_cs NOT NULL,
`v_abc` varchar(32) COLLATE utf8mb4_ja_0900_as_cs GENERATED ALWAYS AS (md5(`abc`)) VIRTUAL NOT NULL,
PRIMARY KEY (`num`),
FULLTEXT KEY `val` (`abc`) /*!50100 WITH PARSER `ngram` */
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_ja_0900_as_cs
1 row in set (0.00 sec)

mysql80 24> ALTER TABLE t1 RENAME COLUMN abc TO def;
ERROR 1054 (42S22): Unknown column 'abc' in 'generated column function'

mysql80 24> ALTER TABLE t1 CHANGE abc def varchar(32) NOT NULL;
ERROR 1054 (42S22): Unknown column 'abc' in 'generated column function'
綺麗に転けるならまだしも、 RENAME TABLEでたまにやるみたいにカラムをスワップさせようと思うと大変なことになる。
mysql80 24> SHOW CREATE TABLE t1\G
*************************** 1. row ***************************
Table: t1
Create Table: CREATE TABLE `t1` (
`num` int(10) unsigned NOT NULL,
`abc` varchar(32) COLLATE utf8mb4_ja_0900_as_cs NOT NULL,
`v_abc` varchar(32) COLLATE utf8mb4_ja_0900_as_cs GENERATED ALWAYS AS (md5(`abc`)) VIRTUAL NOT NULL,
`def` varchar(32) COLLATE utf8mb4_ja_0900_as_cs DEFAULT NULL,
PRIMARY KEY (`num`),
FULLTEXT KEY `val` (`abc`) /*!50100 WITH PARSER `ngram` */
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_ja_0900_as_cs
1 row in set (0.00 sec)

mysql80 24> SELECT * FROM t1;
+-----+-----+----------------------------------+------+
| num | abc | v_abc | def |
+-----+-----+----------------------------------+------+
| 1 | one | f97c5d29941bfb1b2fdab0874906ab82 | NULL |
+-----+-----+----------------------------------+------+
1 row in set (0.00 sec)

mysql80 24> ALTER TABLE t1 RENAME COLUMN abc TO def, RENAME COLUMN def TO abc;
Query OK, 0 rows affected (0.08 sec)
Records: 0 Duplicates: 0 Warnings: 0

mysql80 24> SHOW CREATE TABLE t1\G
*************************** 1. row ***************************
Table: t1
Create Table: CREATE TABLE `t1` (
`num` int(10) unsigned NOT NULL,
`def` varchar(32) COLLATE utf8mb4_ja_0900_as_cs NOT NULL,
`v_abc` varchar(32) COLLATE utf8mb4_ja_0900_as_cs GENERATED ALWAYS AS (md5(`abc`)) VIRTUAL NOT NULL,
`abc` varchar(32) COLLATE utf8mb4_ja_0900_as_cs DEFAULT NULL,
PRIMARY KEY (`num`),
FULLTEXT KEY `val` (`def`) /*!50100 WITH PARSER `ngram` */
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_ja_0900_as_cs
1 row in set (0.00 sec)

mysql80 24> SELECT * FROM t1;
+-----+-----+-------+------+
| num | def | v_abc | abc |
+-----+-----+-------+------+
| 1 | one | | NULL |
+-----+-----+-------+------+
1 row in set (0.01 sec)
VIRTUAL generated columnはビューとかストアドと一緒で名前でしか参照されないので、「カラムが存在しない」以外ではエラーにならなさげ。
…あれ? これSTOREDだとどうなるんだ?
mysql80 24> SHOW CREATE TABLE t1\G
*************************** 1. row ***************************
Table: t1
Create Table: CREATE TABLE `t1` (
`num` int(10) unsigned NOT NULL,
`abc` varchar(32) COLLATE utf8mb4_ja_0900_as_cs NOT NULL,
`def` varchar(32) COLLATE utf8mb4_ja_0900_as_cs DEFAULT NULL,
`v_abc` varchar(32) COLLATE utf8mb4_ja_0900_as_cs GENERATED ALWAYS AS (md5(`abc`)) STORED,
PRIMARY KEY (`num`),
FULLTEXT KEY `val` (`abc`) /*!50100 WITH PARSER `ngram` */
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_ja_0900_as_cs
1 row in set (0.00 sec)

mysql80 24> SELECT * FROM t1;
+-----+-----+------+----------------------------------+
| num | abc | def | v_abc |
+-----+-----+------+----------------------------------+
| 1 | one | NULL | f97c5d29941bfb1b2fdab0874906ab82 |
+-----+-----+------+----------------------------------+
1 row in set (0.00 sec)

mysql80 24> ALTER TABLE t1 RENAME COLUMN abc TO def, RENAME COLUMN def TO abc;
Query OK, 0 rows affected (0.02 sec)
Records: 0 Duplicates: 0 Warnings: 0

mysql80 24> SHOW CREATE TABLE t1\G
*************************** 1. row ***************************
Table: t1
Create Table: CREATE TABLE `t1` (
`num` int(10) unsigned NOT NULL,
`def` varchar(32) COLLATE utf8mb4_ja_0900_as_cs NOT NULL,
`abc` varchar(32) COLLATE utf8mb4_ja_0900_as_cs DEFAULT NULL,
`v_abc` varchar(32) COLLATE utf8mb4_ja_0900_as_cs GENERATED ALWAYS AS (md5(`abc`)) STORED,
PRIMARY KEY (`num`),
FULLTEXT KEY `val` (`def`) /*!50100 WITH PARSER `ngram` */
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_ja_0900_as_cs
1 row in set (0.00 sec)

mysql80 24> SELECT * FROM t1;
+-----+-----+------+----------------------------------+
| num | def | abc | v_abc |
+-----+-----+------+----------------------------------+
| 1 | one | NULL | f97c5d29941bfb1b2fdab0874906ab82 |
+-----+-----+------+----------------------------------+
1 row in set (0.00 sec)
アッー!
ばぐれぽばぐれぽ…φ(・ω・`)

した。もうちょっとわかりやすいテストケースをつけてある。

MySQL Bugs: #92727: Swapping column by ALTER TABLE RENAME COLUMN breaks STORED generated column
ワークアラウンド: OPTIMIZE TABLEしたら再計算されたようで直った
mysql80 24> OPTIMIZE TABLE t1;
+-------+----------+----------+-------------------------------------------------------------------+
| Table | Op | Msg_type | Msg_text |
+-------+----------+----------+-------------------------------------------------------------------+
| d1.t1 | optimize | note | Table does not support optimize, doing recreate + analyze instead |
| d1.t1 | optimize | status | OK |
+-------+----------+----------+-------------------------------------------------------------------+
2 rows in set (0.18 sec)

mysql80 24> SELECT * FROM t1;
+-----+-----+------+-------+
| num | def | abc | v_abc |
+-----+-----+------+-------+
| 1 | one | NULL | NULL |
+-----+-----+------+-------+
1 row in set (0.00 sec)

percona-toolkit 3.0.12とそれ以前のpt-ioprofileがCentOS 7.xで動かない件

$
0
0

TL;DR

  • straceの出力がちょっと変わったのでそれをひっかけられていない
  • Percona Toolkitへのバグレポはこちら
  • パッチは以下のとおり
    • 3.0.12の /usr/bin/pt-ioprofileでは574行目だけど、この行を探せば他のバージョンでも適用できるはず
574c574
< /^Process/ { mode = "strace"; }

> /^(strace: )?Process/ { mode = "strace"; }

ちょっと前からなんかおかしいような気はしていた。
MySQL 8.0だからいけないのかなとか思っていたけど、よく考えれば pt-ioprofileシェルスクリプトだし中で lsofstraceを取って awkで頑張っているだけなので mysqldのバージョンは関係ないはずだ。
CentOSのバージョンのせいかと思って比べてみたらどうやら当たりの様子。
$ cat /etc/centos-release
CentOS release 6.10 (Final)

$ pt-ioprofile
Wed Oct 17 12:24:46 JST 2018
Tracing process ID 365
total pread read pwrite write fsync fdatasync open close getdents lseek fcntl filename
4.430536 0.000000 0.000000 0.159895 0.000000 4.270641 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 /var/lib/mysql/ib_logfile0
0.071731 0.000000 0.000000 0.003155 0.000000 0.061186 0.000000 0.000452 0.000301 0.000000 0.006448 0.000189 /var/lib/mysql/mysqlslap/t1.ibd
0.052828 0.000000 0.000000 0.004702 0.000000 0.048126 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 /var/lib/mysql/ibdata1
0.013654 0.000436 0.001654 0.000204 0.001798 0.000000 0.006763 0.000823 0.000716 0.000000 0.001260 0.000000 /var/lib/mysql/mysqlslap/t1.frm
0.005068 0.000000 0.000000 0.000000 0.000000 0.005068 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 /var/lib/mysql/mysql/innodb_index_stats.ibd
0.002342 0.000000 0.000000 0.000000 0.000000 0.002342 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 /var/lib/mysql/mysql/innodb_table_stats.ibd
0.000917 0.000000 0.000176 0.000000 0.000000 0.000000 0.000000 0.000323 0.000233 0.000185 0.000000 0.000000 /dev/urandom
0.000911 0.000000 0.000000 0.000000 0.000000 0.000000 0.000080 0.000549 0.000141 0.000141 0.000000 0.000000 /var/lib/mysql/mysqlslap/
0.000628 0.000000 0.000000 0.000000 0.000140 0.000000 0.000000 0.000376 0.000112 0.000000 0.000000 0.000000 /var/lib/mysql/mysqlslap/db.opt
0.000400 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000213 0.000000 0.000000 0.000187 0.000000 /var/lib/mysql/mysql/event.MYD
0.000126 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000126 0.000000 0.000000 0.000000 0.000000 /var/lib/mysql/mysql/proc.MYD
同じMySQL 5.7.23にmysqlslapをかけながら pt-ioprofileをぶっ放しても、CentOS 7.5の方は何も返してくれない。
$ cat /etc/centos-release
CentOS Linux release 7.5.1804 (Core)

$ pt-ioprofile
Wed Oct 17 12:07:17 JST 2018
Tracing process ID 27170
total filename
取り敢えずまあ採取した stracelsofの結果を捨てずに残しておく --save-samplesオプションなど使いつつ、 bash -xで何をしているのか調べていく。
$ bash -x /usr/bin/pt-ioprofile --save-samples /tmp/
..
+ _lsof 27170
..
+ strace -T -s 0 -f -p 27170
..
+ awk -f /tmp/pt-ioprofile.7393.52pjmz/tabulate_strace.awk /tmp/pt-ioprofile
+ awk -f /tmp/pt-ioprofile.7393.52pjmz/summarize_strace.awk /tmp/pt-ioprofile.7393.52pjmz/tabulated_samples
+ '[' filename '!=' all ']'
+ head -n1 /tmp/pt-ioprofile.7393.52pjmz/summarized_samples
total filename
+ tail -n +2 /tmp/pt-ioprofile.7393.52pjmz/summarized_samples
+ sort -rn -k1
+ rm_tmpdir
+ '[' -n /tmp/pt-ioprofile.7393.52pjmz ']'
+ '[' -d /tmp/pt-ioprofile.7393.52pjmz ']'
+ rm -rf /tmp/pt-ioprofile.7393.52pjmz
+ PT_TMPDIR=
+ exit 0

$ ll /tmp/pt-ioprofile
-rw-r--r-- 1 yoku0825 yoku0825 128606368 Oct 17 12:48 /tmp/pt-ioprofile
lsofstraceはちゃんと取れているっぽい。
$ less /tmp/pt-ioprofile
+ local pid=27170
+ lsof -p 27170
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
mysqld 27170 yoku0825 cwd DIR 253,0 4096 134572596 /usr/mysql/5.7.23/data
mysqld 27170 yoku0825 rtd DIR 253,0 4096 128 /
mysqld 27170 yoku0825 txt REG 253,0 686787152 201877015 /usr/mysql/5.7.23/bin/mysqld
..
strace: Process 27170 attached with 36 threads
[pid 27203] futex(0x7ff4ec03ded4, FUTEX_WAIT_PRIVATE, 347, NULL <unfinished ...>
[pid 27202] futex(0x7ff4ec03de44, FUTEX_WAIT_PRIVATE, 391, NULL <unfinished ...>
[pid 27201] futex(0x7ff4ec03ddb4, FUTEX_WAIT_PRIVATE, 455, NULL <unfinished ...>
..
じゃあまあなんか集計部分がおかしいのかな、と awkを叩いてみようと思うも
$ awk -f /tmp/pt-ioprofile.7393.52pjmz/tabulate_strace.awk /tmp/pt-ioprofile
awk: fatal: can't open source file `/tmp/pt-ioprofile.7393.52pjmz/tabulate_strace.awk' for reading (No such file or directory)
(・3・)アルェー
と思ったら、 bash -xの最後の方で /tmp/pt-ioprofile.7393.52pjmzを消してた。
どうやら一度テンポラリーディレクトリーにawkスクリプトを書き出した後にawk -fで食わせているっぽい。
rm_tmpdirから rm -rfの部分を消し去ってリトライ。
$ bash -x /usr/bin/pt-ioprofile --save-samples /tmp/pt-ioprofile
..
+ awk -f /tmp/pt-ioprofile.7897.sdNdwz/tabulate_strace.awk /tmp/pt-ioprofile
+ awk -f /tmp/pt-ioprofile.7897.sdNdwz/summarize_strace.awk /tmp/pt-ioprofile.7897.sdNdwz/tabulated_samples
..

$ ll /tmp/pt-ioprofile.7897.sdNdwz/
total 12
-rw-r--r-- 1 yoku0825 yoku0825 20 Oct 17 12:57 summarized_samples
-rw-r--r-- 1 yoku0825 yoku0825 3408 Oct 17 12:57 summarize_strace.awk
-rw-r--r-- 1 yoku0825 yoku0825 0 Oct 17 12:57 tabulated_samples
-rw-r--r-- 1 yoku0825 yoku0825 3547 Oct 17 12:57 tabulate_strace.awk

$ awk -f /tmp/pt-ioprofile.7897.sdNdwz/tabulate_strace.awk /tmp/pt-ioprofile
あれ何も起こらない…
比較用のCentOS 6.10では *_samplesも0バイトじゃないし、 awkの結果も返ってくる。
$ ll /tmp/pt-ioprofile.812.PmCXAO
total 164
-rw-r--r-- 1 root root 1765 Oct 17 12:59 summarized_samples
-rw-r--r-- 1 root root 3408 Oct 17 12:59 summarize_strace.awk
-rw-r--r-- 1 root root 154779 Oct 17 12:59 tabulated_samples
-rw-r--r-- 1 root root 3547 Oct 17 12:59 tabulate_strace.awk

$ awk -f /tmp/pt-ioprofile.812.PmCXAO/tabulate_strace.awk /tmp/pt-ioprofile
517 open 62 0 0.000032 /dev/urandom
517 read 62 32 0.000016 /dev/urandom
517 close 62 0 0.000015 /dev/urandom
517 open directory) 0 0.000730 /var/lib/mysql/mysqlslap/
517 open 62 0 0.000079 /var/lib/mysql/mysqlslap/db.opt
517 write 62 65 0.000024 /var/lib/mysql/mysqlslap/db.opt
..
ということで、 tabulate_strace.awkのどこかが悪そう。
…というのを頑張った結果、最初に書いたパッチにたどり着いたのでしたん。

MySQL 8.0.13の新機能でPRIMARY KEYのないテーブルを作成させない

$
0
0

TL;DR

  • sql_require_primary_keyサーバー変数をONにすると、PRIMARY KEYのないテーブルを作ろうとした時にエラーにできる。
    • セッションスコープとグローバルスコープと両方あるやつで、実効値はセッションスコープなので注意。
    • ただし、 SET SESSION ..でも一般ユーザーでは値を変更することはできない( sql_log_binとかもそうですね)
  • 超便利だ!! 秘伝のタレに入れる時は looseプレフィックスとかもいいと思うよ!!

取り敢えず基本的な使い方として、0(OFF)と1(ON)の時の動作の違い。
mysql80 9> SELECT @@sql_require_primary_key;
+---------------------------+
| @@sql_require_primary_key |
+---------------------------+
| 0 |
+---------------------------+
1 row in set (0.00 sec)

mysql80 9> CREATE TABLE t1 (num int);
Query OK, 0 rows affected (0.04 sec)

mysql80 9> SET @@session.sql_require_primary_key= 1;
Query OK, 0 rows affected (0.00 sec)

mysql80 9> SELECT @@sql_require_primary_key;
+---------------------------+
| @@sql_require_primary_key |
+---------------------------+
| 1 |
+---------------------------+
1 row in set (0.00 sec)

mysql80 9> CREATE TABLE t2 (num int);
ERROR 3750 (HY000): Unable to create a table without PK, when system variable 'sql_require_primary_key' is set. Add a PK to the table or unset this variable to avoid this message. Note that tables without PK can cause performance problems in row-based replication, so please consult your DBA before changing this setting.
おお、マニュアル通り。
Note that tables without PK can cause performance problems in row-based replication, so please consult your DBA before changing this setting.にちょっとニヤリとするwww
しかし、セッションとグローバルと両方持ってるってことは一般ユーザーで値書き換えられちゃうんでは? と思ったけど、それができない類の変数になっていた(昔から sql_log_binとかもセッション変数だけど一般ユーザーは変更できないのでそれと同じところを通ってるのであろう)
mysql80 10> SHOW GRANTS;
+--------------------------------------------------+
| Grants for yoku0825@% |
+--------------------------------------------------+
| GRANT USAGE ON *.* TO `yoku0825`@`%` |
| GRANT ALL PRIVILEGES ON `d1`.* TO `yoku0825`@`%` |
+--------------------------------------------------+
2 rows in set (0.00 sec)

mysql80 10> SET @@session.sql_require_primary_key= 1;
ERROR 1227 (42000): Access denied; you need (at least one of) the SUPER or SYSTEM_VARIABLES_ADMIN privilege(s) for this operation
これで勝手にPK必須が剥がれる心配もなく。
ちなみに、これがONの時にどれくらいPKを強制されるかというと
mysql80 11> OPTIMIZE TABLE t2;
+-------+----------+----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table | Op | Msg_type | Msg_text |
+-------+----------+----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| d1.t2 | optimize | note | Table does not support optimize, doing recreate + analyze instead |
| d1.t2 | optimize | error | Unable to create a table without PK, when system variable 'sql_require_primary_key' is set. Add a PK to the table or unset this variable to avoid this message. Note that tables without PK can cause performance problems in row-based replication, so please consult your DBA before changing this setting. |
| d1.t2 | optimize | status | Operation failed |
+-------+----------+----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
3 rows in set, 1 warning (0.00 sec)

mysql80 11> ALTER TABLE t2 ADD val varchar(32);
ERROR 3750 (HY000): Unable to create a table without PK, when system variable 'sql_require_primary_key' is set. Add a PK to the table or unset this variable to avoid this message. Note that tables without PK can cause performance problems in row-based replication, so please consult your DBA before changing this setting.

mysql80 11> ALTER TABLE t2 ADD KEY(num);
ERROR 3750 (HY000): Unable to create a table without PK, when system variable 'sql_require_primary_key' is set. Add a PK to the table or unset this variable to avoid this message. Note that tables without PK can cause performance problems in row-based replication, so please consult your DBA before changing this setting.

mysql80 11> INSERT INTO t2 VALUES (2);
Query OK, 1 row affected (0.01 sec)
ALTER TABLE関連( OPTIMIZE TABLEもInnoDBでは ALTER TABLEにマッピングされるので同類とする)は全滅。
Sql_cmd_alter_table::execute -> mysql_alter_table -> create_table_impl -> mysql_prepare_create_tableでエラーになるので、 ALTER TABLEだけが引っ掛かりそうだしDMLは影響を受けなさそう。
ちなみにストレージエンジン問わないので、ONにしたままだとCSVストレージエンジンは身動きが取れない(*ノ∀ノ)
mysql80 12> CREATE TABLE t3 (num int) Engine= CSV;
ERROR 3750 (HY000): Unable to create a table without PK, when system variable 'sql_require_primary_key' is set. Add a PK to the table or unset this variable to avoid this message. Note that tables without PK can cause performance problems in row-based replication, so please consult your DBA before changing this setting.

mysql80 12> CREATE TABLE t3 (num int, PRIMARY KEY(num)) Engine= CSV;ERROR 1069 (42000): Too many keys specified; max 0 keys allowed

MySQL 8.0.13とそれ以降で「パスワード変更の際に今のパスワードを入力させる」オプション

$
0
0

TL;DR

if (mysqlスキーマへのUPDATE権限 || CREATE USER権限)
return パスワード確認不要;
else
{
if (global.password_require_currentがON || そのアカウントが mysql.user.Password_require_current = 'Y'になっている)
return パスワード確認必要;
else
return パスワード確認不要;
}
  • パスワード確認必要な場合、パスワードを変えるようなステートメントの最後に REPLACE '元のパスワード'をつける
    • 対話的に聞かれるわけではない
    • REPLACEをつけないと MySQL error code MY-013226 (ER_MISSING_CURRENT_PASSWORD): Current password needs to be specified in the REPLACE clause in order to change it.と言われる
  • これをよく読むと実はちゃんと書いてある

最初、ずっと root@localhostでやってて全然反映されないな…と思ってたら、 特定の権限がある場合はこれ効かないのであった。一般ユーザーのみ。
mysql80 28> SELECT user, host, password_require_current FROM mysql.user WHERE user = 'yoku0825';
+----------+------+--------------------------+
| user | host | password_require_current |
+----------+------+--------------------------+
| yoku0825 | % | NULL |
+----------+------+--------------------------+
1 row in set (0.00 sec)
mysql.user.Password_require_currentの取りうる値はNULL(NULLは値じゃない…けど取り敢えず許して),Y,N` 。
Yは「REPLACE句がないとエラー」、 Nは「REPLACE句はあってもなくても良い」(ただし、REPLACE句を書いた上で元のパスワードを間違えるとエラー)、 NULLは「 global.password_require_currentの値に従う」。
ユーザー作成時の暗黙のデフォルトは NULL
Y, N, NULLに対応する ALTER USERステートメントはこんな感じで、こっちの句を見るとそれぞれ要求する、あってもなくてもいい、システム設定に従う、みたいな感じがして良い。
mysql80 28> ALTER USER yoku0825 PASSWORD REQUIRE CURRENT; -- 'Y'にする
Query OK, 0 rows affected (0.04 sec)

mysql80 28> SELECT user, host, password_require_current FROM mysql.user WHERE user = 'yoku0825';
+----------+------+--------------------------+
| user | host | password_require_current |
+----------+------+--------------------------+
| yoku0825 | % | Y |
+----------+------+--------------------------+
1 row in set (0.00 sec)

mysql80 28> ALTER USER yoku0825 PASSWORD REQUIRE CURRENT OPTIONAL; -- 'N'にする
Query OK, 0 rows affected (0.03 sec)

mysql80 28> SELECT user, host, password_require_current FROM mysql.user WHERE user = 'yoku0825';
+----------+------+--------------------------+
| user | host | password_require_current |
+----------+------+--------------------------+
| yoku0825 | % | N |
+----------+------+--------------------------+
1 row in set (0.00 sec)

mysql80 28> ALTER USER yoku0825 PASSWORD REQUIRE CURRENT DEFAULT; -- NULLにする
Query OK, 0 rows affected (0.07 sec)

mysql80 28> SELECT user, host, password_require_current FROM mysql.user WHERE user = 'yoku0825';
+----------+------+--------------------------+
| user | host | password_require_current |
+----------+------+--------------------------+
| yoku0825 | % | NULL |
+----------+------+--------------------------+
1 row in set (0.00 sec)
最初のサンプルっぽいところで パスワード確認必要になる組み合わせで、REPLACE句を指定しないでパスワードを触ろうとするとこうなる。
mysql80 31> SET PASSWORD = 'yoku0825';
ERROR 13226 (HY000): Current password needs to be specified in the REPLACE clause in order to change it.

mysql80 31> SET PASSWORD = 'yoku0825' REPLACE 'old_password';
Query OK, 0 rows affected (0.03 sec)
ただし、権限があるアカウントでも パスワード確認不要の組み合わせでも、もとのパスワードを間違えるとこうなる。
mysql80 31> SET PASSWORD = 'yoku0826' REPLACE 'wrong_password';
ERROR 13225 (HY000): Incorrect current password. Specify the correct password which has to be replaced.

utf8mb4_0900_ai_ciは "="と "≠"を同じ文字だと思っている

$
0
0

TL;DR


MySQL 8.0の utf8mb4のデフォルト照合順序として utf8mb4_0900_ai_ciというのがあって( default_collation_for_utf8mb4で多少は変えられる)、これは kamipoのハハ=パパ問題を引き起こす照合順序として日本人には知られている(と思う。といいな。広まれ!)
で、その utf8mb4_0900_ai_ciが今度はイコール=ノットイコール問題を発症したらしい。
mysql80 34> SELECT '=' = '≠';
+-------------+
| '=' = '≠' |
+-------------+
| 1 |
+-------------+
1 row in set (0.00 sec)
…はは。
ちょっと気になって調べてみた感じ、どうもUCA準拠の照合順序では 最初っからそうだったっぽい
MySQL 5.0ですらこれは引っ掛かる。
mysql50> SELECT '=' = '≠' COLLATE utf8_unicode_ci;
+-------------------------------------+
| '=' = '≠' COLLATE utf8_unicode_ci |
+-------------------------------------+
| 1 |
+-------------------------------------+
1 row in set (0.00 sec)

mysql50> SELECT '=' = '≠' COLLATE utf8_general_ci;
+-------------------------------------+
| '=' = '≠' COLLATE utf8_general_ci |
+-------------------------------------+
| 0 |
+-------------------------------------+
1 row in set (0.00 sec)
今まで暗黙のデフォルトが「引っ掛からない」方だったから気にならなかった、というだけのアレだったようですね。
ちなみにどんな文字が同一視されるか、については 日本MySQLユーザ会の代表えもんこと @tmtmsさんがかなり昔にまとめてくれていましたね。
ありがたやありがたや(*-人-)

MySQL 8.0.13でカラム定義のDEFAULTに関数が指定できるようになった

$
0
0
みんなだいすき DEFAULTがついに関数を指定できるようになった。
8.0.12とそれ以前はリテラルのみが指定可能、例外として TIMESTAMP, DATETIME型の CURRENT_TIMESTAMPのみだった。
記法は .. DEFAULT ( expression )で、 DEFAULTのあとに括弧を入れてから関数なり表現なりを書く。
The MySQL 8.0.13 Maintenance Release is Generally Available | MySQL Server Blogに書いてある↓をそのまま試そうとしても、括弧が抜けているので通らない。。
mysql80 40> CREATE TABLE t2 (a BINARY(16) DEFAULT uuid_to_bin(uuid()));
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 'uuid_to_bin(uuid()))' at line 1

mysql80 40> CREATE TABLE t2 (a BINARY(16) DEFAULT (uuid_to_bin(uuid())));
Query OK, 0 rows affected (0.11 sec)
( ´-`).oO(MySQL Server Teamのブログではまれにだがよくあること
mysql80 40> CREATE TABLE t1 (num serial, val varchar(32), val_len int NOT NULL DEFAULT (CHARACTER_LENGTH(val)));
Query OK, 0 rows affected (0.10 sec)

mysql80 40> INSERT INTO t1 (num, val) VALUES (1, 'one');
Query OK, 1 row affected (0.07 sec)

mysql80 40> SELECT * FROM t1;
+-----+------+---------+
| num | val | val_len |
+-----+------+---------+
| 1 | one | 3 |
+-----+------+---------+
1 row in set (0.00 sec)
しかしこれ迂闊にNOT NULLなカラムのデフォルトにNULLアンセーフな関数とNULLABLEなカラムの値を組み合わせるとおかしなことになった。
mysql80 40> INSERT INTO t1 (num, val) VALUES (2, NULL);
Query OK, 1 row affected, 1 warning (0.06 sec)

mysql80 40> SHOW WARNINGS;
+-------+------+---------------------------------+
| Level | Code | Message |
+-------+------+---------------------------------+
| Error | 1048 | Column 'val_len' cannot be null |
+-------+------+---------------------------------+
1 row in set (0.00 sec)

mysql80 40> SELECT * FROM t1;
+-----+------+---------+
| num | val | val_len |
+-----+------+---------+
| 1 | one | 3 |
| 2 | NULL | 0 |
+-----+------+---------+
2 rows in set (0.00 sec)
CHARACTER_LENGTH(NULL)NULLなので、NOT NULLな val_lenのデフォルト値が NULLになるというなんか地獄のように矛盾した結果、INT型のフォールバック先である0に落ち着いた様子。
ただこれ STRICT_TRANS_TABLESの状態でこの動作になっちゃうのでちょっとあんまり嬉しくない(同じことをデフォルト値を使わずにやるとちゃんとエラーになるのに…)
mysql80 40> SELECT @@sql_mode;
+-----------------------------------------------------------------------------------------------------------------------+
| @@sql_mode |
+-----------------------------------------------------------------------------------------------------------------------+
| ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION |
+-----------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

mysql80 40> INSERT INTO t1 (num, val, val_len) VALUES (3, NULL, CHARACTER_LENGTH(NULL));
ERROR 1048 (23000): Column 'val_len' cannot be null
使い道があんまり思いつかない(generated columnでもいいケースがほとんど?)けれど、ブログのサンプルにもあった uuid_to_bin(uuid())を使って時間経過順に並ぶ形式にしたUUIDをサロゲートキーにする、とかとても上手く使えそう。

MySQL 8.0.13の式インデックス

$
0
0
嬉し楽しい式インデックス。
PostgreSQLのこれが結構うらやましかった機能がついにMySQLにも!
MySQL 5.7からgenerated columnが入ってそのカラムにインデックスを張ればそれっぽい高速化は実現できたんだけれども、generated columnは如何せんORMと相性が悪いことがあって(ORMはそのカラムがgeneratedかbasicか特に気にしてくれないけど、generatedなカラムは更新しようとするとエラーになる、など)そういうケースではカラムを定義せずに式インデックスが使えるといいのに…と思っていたのでしたん。
という訳で式インデックス、定義の仕方はこちら。
ALTER TABLE .. ADD KEY ..CREATE INDEX ..の、普段なら (カラム名, ..)になっているところを ((式), ..)にするだけ。式そのものを括弧でくくるのを忘れずに。
mysql80 41> SELECT * FROM t1;
+------+-------+
| num | val |
+------+-------+
| 1 | one |
| 2 | two |
| 3 | three |
| 4 | four |
| 5 | five |
+------+-------+
5 rows in set (0.00 sec)

mysql80 41> SHOW CREATE TABLE t1\G
*************************** 1. row ***************************
Table: t1
Create Table: CREATE TABLE `t1` (
`num` int(11) NOT NULL,
`val` varchar(32) COLLATE utf8mb4_ja_0900_as_cs DEFAULT NULL,
PRIMARY KEY (`num`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_ja_0900_as_cs
1 row in set (0.00 sec)
こんなテーブルがあるじゃろ?
mysql80 41> EXPLAIN SELECT * FROM t1 WHERE num = 2;
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| 1 | SIMPLE | t1 | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

mysql80 41> EXPLAIN SELECT * FROM t1 WHERE num + 1 = 3;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | t1 | NULL | ALL | NULL | NULL | NULL | NULL | 5 | 100.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
num + 1とか左辺に演算子がくるとインデックスが使えんじゃろ?
mysql80 41> ALTER TABLE t1 ADD KEY ((num + 1));
Query OK, 0 rows affected (0.06 sec)
Records: 0 Duplicates: 0 Warnings: 0

mysql80 41> SHOW CREATE TABLE t1\G
*************************** 1. row ***************************
Table: t1
Create Table: CREATE TABLE `t1` (
`num` int(11) NOT NULL,
`val` varchar(32) COLLATE utf8mb4_ja_0900_as_cs DEFAULT NULL,
PRIMARY KEY (`num`),
KEY `functional_index` (((`num` + 1)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_ja_0900_as_cs
1 row in set (0.00 sec)

mysql80 41> EXPLAIN SELECT * FROM t1 WHERE num + 1 = 3;
+----+-------------+-------+------------+------+------------------+------------------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+------------------+------------------+---------+-------+------+----------+-------+
| 1 | SIMPLE | t1 | NULL | ref | functional_index | functional_index | 8 | const | 1 | 100.00 | NULL |
+----+-------------+-------+------------+------+------------------+------------------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
関数インデックスを使うとそれが引けるんじゃ。
しかしこれ、オプティマイザーでたたんでくれるわけではなさそうなので、 WHEREORDER BYに出てくる式と一致しないといけないっぽい。
ASを使ったエイリアスに ORDER BYからアクセスするのはいけた。
mysql80 41> EXPLAIN SELECT * FROM t1 WHERE num + 2 - 1 = 3;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | t1 | NULL | ALL | NULL | NULL | NULL | NULL | 5 | 100.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

mysql80 41> EXPLAIN SELECT num + 1 AS c FROM t1 ORDER BY c;
+----+-------------+-------+------------+-------+---------------+------------------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+------------------+---------+------+------+----------+-------------+
| 1 | SIMPLE | t1 | NULL | index | NULL | functional_index | 8 | NULL | 5 | 100.00 | Using index |
+----+-------------+-------+------------+-------+---------------+------------------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
ユニークキーは作れたけど、全文検索、あまりに変な(?)関数、 NOW(), AUTO_INCREMENTな列に対する関数はエラーになって弾かれる。
mysql80 41> ALTER TABLE t1 ADD FULLTEXT KEY ((num + 3));
ERROR 3759 (HY000): Fulltext functional index is not supported.

mysql80 41> ALTER TABLE t1 ADD KEY ((SLEEP(1)));
ERROR 3758 (HY000): Expression of functional index 'functional_index_4' contains a disallowed function.

mysql80 41> ALTER TABLE t1 ADD KEY ((RAND(1)));
ERROR 3758 (HY000): Expression of functional index 'functional_index_4' contains a disallowed function.

mysql80 41> ALTER TABLE t1 ADD KEY ((NOW(1)));
ERROR 3758 (HY000): Expression of functional index 'functional_index_4' contains a disallowed function.
個人的にはアレかな、 idx_status ((status IN (1, 2, 3)), (activate <> 1))みたいな式インデックスを作ると捗るかなと思いました!

MySQLのエラーコード1133、ER_PASSWORD_NO_MATCH "Can't find any matching row in the user table"について

$
0
0

TL;DR

  • sql_mode= NO_AUTO_CREATE_USERが指定されている時に CREATE USERせずに GRANT ..で直接ユーザーを作ろうとした
  • SET PASSWORD FOR user@host = ..で存在しないユーザーのパスワードを変更しようとした
  • mysql.user.pluginのカラムが空文字列のアカウントに GRANTALTER USERをかけようとした
  • mysql.userテーブルに INSERTなり UPDATEなりをした後、 FLUSH PRIVILEDGESをしていないのでアカウントとして認識されていない

  • sql_mode= NO_AUTO_CREATE_USERが指定されている時に CREATE USERせずに GRANT ..で直接ユーザーを作ろうとした
昔、「MySQLのアカウントは GRANTステートメントで作るんだよ」って教えられたような気がするんだけど、それはもう過去の話になってしまったようだ。
MySQL 5.7とそれ以降はデフォルトの sql_modeNO_AUTO_CREATE_USERが指定されており、これが有効だと「存在しないアカウントに対する GRANT」が転けるようになる。
先に CREATE USERでアカウントを作ってから GRANTするか、sql_modeから NO_AUTO_CREATE_USERを取り除いてやれるかばいいんだけど、MySQL 8.0.11とそれ以降では sql_modeからそもそも NO_AUTO_CREATE_USERなくなっており、今後常に「存在しないアカウントに対する GRANT」は転け続けるので、sql_modeから取り除く方はお勧めしない。
  • SET PASSWORD FOR user@host = ..で存在しないユーザーのパスワードを変更しようとした
これはエラーメッセージそのまま。ユーザーテーブル(= mysql.userのこと)に行が見つからないよ、ってこと。
  • mysql.user.pluginのカラムが空文字列のアカウントに GRANTALTER USERをかけようとした
MySQL 5.5とそれ以降では mysql.userテーブルに pluginというカラムが追加されていて、はここに「どのプラグインを使ってユーザー認証をするか」(パスワードハッシュ形式を指定したり、ソケット認証とかPAM認証とかを指定したりする)を記録する。
5.5, 5.6ではNULLABLEで空文字列許可だったけれど5.7から先はここが空っぽだとくだんのエラーで転けるようになる。
本来 pluginを変更するために使える ALTER USERステートメントも失敗するようになるので、 mysql.user.pluginUPDATEして FLUSH PRIVILEGESすることになる。
mysql57 4> SELECT plugin FROM mysql.user WHERE user = 'yoku0825';
+--------+
| plugin |
+--------+
| |
+--------+
1 row in set (0.00 sec)

mysql57 4> GRANT SELECT ON *.* TO yoku0825;
ERROR 1133 (42000): Can't find any matching row in the user table

mysql57 4> UPDATE mysql.user SET plugin = 'mysql_native_password' WHERE user = 'yoku0825';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0

mysql57 4> SELECT plugin FROM mysql.user WHERE user = 'yoku0825';
+-----------------------+
| plugin |
+-----------------------+
| mysql_native_password |
+-----------------------+
1 row in set (0.00 sec)

mysql57 4> FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.00 sec)

mysql57 4> GRANT SELECT ON *.* TO yoku0825;
Query OK, 0 rows affected (0.00 sec)
  • mysql.userテーブルに INSERTなり UPDATEなりをした後、 FLUSH PRIVILEDGESをしていないのでアカウントとして認識されていない
mysqldump --all-databasesからのリストアにありがちなこと。
mysql.userテーブルへの更新ステートメントはACLを更新しないので、リストアして再起動も FLUSH PRIVILEGESも叩かないと mysql.user上には存在するけどACL上には存在しないので (;・3・) アルェ ってなるやつになったりする。
mysql.userテーブルと、実際に認証に使われるACLの関係については↓の アカウント情報と mysql.user テーブルの同期のあたりで詳しく(?)解説しています。
( ´-`).oO(結構前の記事だけど割と的を射てると思う…おきにいり。

MySQL 8.0のcaching_sha2_password + 非SSL接続が転ける

$
0
0
$ mysql80 -h 127.0.0.1 -u yoku0825 --ssl-mode=disabled -p
Enter password:
ERROR 2061 (HY000): Authentication plugin 'caching_sha2_password' reported error: Authentication requires secure connection.
このエラーになる条件。
  • caching_sha2_passwordプラグインを使っているアカウント
    かつ
  • まだサーバー側にSHA2キャッシュが作られていないアカウント
    かつ
  • サーバーの公開鍵を指定していない非SSLのTCP接続
この条件に合致しているとログインが転けて↑のエラーを食らう。

解決策1. caching_sha2_passwordプラグインをやめて mysql_native_passwordプラグインを指定する

mysql> ALTER USER yoku0825 IDENTIFIED WITH mysql_native_password BY 'new_password`;

解決策2. 一度ログインに成功すればサーバー側にSHA2キャッシュが作成されるので、一度「エラーになる条件を満たさない」接続をしてやる

$ mysql80 -h localhost -S /usr/mysql/8.0.13/data/mysql.sock -u yoku0825 -p ## ソケット接続
$ mysql80 -h 127.0.0.1 -u yoku0825 --ssl-mode=PREFERRED -p ## SSL接続
$ mysql80 -h 127.0.0.1 -u yoku0825 --ssl-mode=disabled -p --server-public-key-path=/usr/mysql/8.0.13/data/public_key.pem ## サーバー公開鍵を指定した非SSL接続

解決策3. サーバーの公開鍵を指定する

↑と一緒で、 mysqlコマンドラインクライアントだとこう。

$ mysql80 -h 127.0.0.1 -u yoku0825 --ssl-mode=disabled -p --server-public-key-path=/usr/mysql/8.0.13/data/public_key.pem ## サーバー公開鍵を指定した非SSL接続
のいずれかをやればOK。

要は、 caching_sha2_passwordだと初回の認証成功時にパスワードキャッシュをサーバーサイドに作るらしいんだけど、そこでMITM攻撃を食らうとマズいからこんな仕様になっているらしい。
その理屈(どうして1回目だけがmysql_native_passwordとかと比べてダメなのか)はよくわからないんだけど誰か読んで教えてくだしあ(ソースを読まないとこれ以上の理解は出てこないような気もする)
なお、
  • mysqldの再起動
  • FLUSH PRIVILEGES
  • キャッシュはアカウント単位なので、 CREATE USER直後
もこれ(SHA2キャッシュがない状態)にあたるので、おとなしくSSL接続使うようにした方が良いと思われるのでした。
なお、このネタはご覧のスポンサー(?)の提供でお送りしました














MySQLのロール周りのあれこれ

$
0
0

TL;DR

  • デフォルトロールはログイン時に有効化
  • mandatory_rolesはログイン時に有効化されない、全ユーザーから SET ROLEできるロール
  • ロールのホスト部は意味を持たないような気がする( role@127.0.0.1192.168.0.0なアカウントにも割り当てができる)
  • activate_all_roles_on_login = ONにすると、デフォルトロールも割り当てたロールも mandatory_rolesも全部いっぺんにログイン時に有効になる
ロールの有効化ロールの作成ロールの割り当て割り当ての解除
ロールSET ROLECREATE ROLEGRANT REVOKE
デフォルトロールログイン時CREATE ROLECREATE USER .. DEFAULT ROLE または GRANT && ALTER USER .. DEFAULT ROLEREVOKE
必須ロールSET ROLECREATE ROLEmandatory_rolesを設定した時に自動でmandatory_rolesからなくなった時に自動で

デフォルトロールとか必須ロールとかごちゃごちゃしてきたのでメモ。
  • フツーに CREATE ROLEして CREATE USER ..&& GRANT <role_name>で与える場合、 GRANTしたタイミングで mysql.role_edgesINSERTされる
  • CREATE ROLEして CREATE USER .. DEFAULT ROLE ..した場合、 CREATE USERのタイミングで mysql.role_edgesに入る
  • SET GLOBAL mandatory_roles ..の場合は mysql.role_edgesには入らない
存在しないロールを SET GLOBAL mandatory_rolesで指定すると↓のエラーになる(ただしステートメントはエラーにならず、エラーログにピヨッと現れるだけ)
2018-10-29T02:23:39.444341Z 108 [Warning] [MY-010968] [Server] Can't set mandatory_role: There's no such authorization ID role@localhost.
mandatory_rolesREVOKEしたり DROP ROLEしようとしたりすると怒られる。
mysql80 120> REVOKE role@localhost FROM yoku0825;
ERROR 3628 (HY000): The role `role`@`localhost` is a mandatory role and can't be revoked or dropped. The restriction can be lifted by excluding the role identifier from the global variable mandatory_roles.
mandatory_rolesはコンマ区切りで複数指定可能。
ただし、ロールのホスト部でフィルタリングがかけられるわけではない。
mysql80 127> SET GLOBAL mandatory_roles = 'role@1.2.3.4,role@localhost';
Query OK, 0 rows affected (0.02 sec)

$ mysql80 -uyoku0825
mysql80 130> SHOW GRANTS;
+---------------------------------------------------------------------+
| Grants for yoku0825@localhost |
+---------------------------------------------------------------------+
| GRANT USAGE ON *.* TO `yoku0825`@`localhost` |
| GRANT `role`@`1.2.3.4`,`role`@`localhost` TO `yoku0825`@`localhost` |
+---------------------------------------------------------------------+
2 rows in set (0.00 sec)

mysql80 130> SET ROLE role@localhost;
Query OK, 0 rows affected (0.00 sec)

mysql80 130> SET ROLE role@1.2.3.4; <-- localhost != 1.2.3.4 だけど判定されないSET ROLEできる
Query OK, 0 rows affected (0.00 sec)

とするとアレかね、
  • GRANT SELECT ON d1.* TO read_only_role
  • SET GLOBAL mandatory_roles = read_only_role
  • GRANT ALL ON d1.* TO read_write_role
  • CREATE USER apuser DEFAULT ROLE read_write_role
  • CREATE USER dev1 IDENTIFIED BY ..
とかやると、 apuserはデフォルトで読み書き可能、新規追加するアカウント(たとえば dev1の次に dev2とか)はデフォルトで読み取りのみ可能(なロールに SET ROLEできる)、とかの表現になるのか。
あるいは、 activate_all_roles_on_login = ONしておくと SET ROLE read_only_roleとかしなくてもデフォルトで読み取りだけ可能とかになる、と。

mysql.userテーブルの認証周りのカラムあれこれ

$
0
0

TL;DR

versionpasswordカラム(CHAR(41) NOT NULL)authentication_stringカラム(TEXT NULL)pluginカラム認証プラグインの選択
5.0.96パスワードハッシュカラムなしカラムなしダイジェスト長
5.1.73パスワードハッシュカラムなしカラムなしダイジェスト長
5.5.62パスワードハッシュ常に空文字認証プラグインpluginカラム、空文字列の場合はダイジェスト長
5.6.42SHA256プラグイン以外のパスワードハッシュSHA256プラグインの時のパスワードハッシュ認証プラグインpluginカラム、空文字列の場合はダイジェスト長
5.7.24カラムなしパスワードハッシュ認証プラグインpluginカラムのみ
8.0.13カラムなしパスワードハッシュ認証プラグインpluginカラムのみ

日々の覚書: MySQL 5.7.6でmysql.userテーブルのパスワードのカラム名がなんか変わったで「MySQL 5.7ではパスワードハッシュが格納されるカラムが passwordから authentication_stringに変更になった」としていたけれど、ちょっと調べてみたらなんかそれなりに歴史的経緯っぽいものがありました。
  • 旧来(MySQL 5.0, 5.1)は2種類の認証プラグイン( mysql_native_password = 俗称41桁ハッシュ、 mysql_old_password = 俗称16桁ハッシュ) があったが、それを識別するための pluginカラムは無く、「クライアントから送られてきたダイジェストの長さ」でどちらのプラグインを使うか決めて passwordカラムに入っているパスワードハッシュを引き出して使っていた
  • MySQL 5.5から pluginカラムが追加される。これは5.5で「認証プラグインAPI」を解放した時に一緒に実装されたのだと思う。
    • MySQL :: MySQL 5.5 Reference Manual :: 6.5.1 Authentication Plugins
    • たぶん過渡期の例外措置として、 pluginが空文字ならば旧来と同じダイジェストの長さによって mysql_native_passwordmysql_old_passwordを打ち分ける実装になっている
    • authentication_stringカラムも実装されたけど、この時点でこのカラムを使っているっぽい認証プラグインは少なくともコミュニティー版のソースコード上にはない。
  • MySQL 5.6では商用版限定ながら sha256_passwordプラグインが登場する。
    • MySQL :: MySQL 5.6 Reference Manual :: 6.5.1.4 SHA-256 Pluggable Authentication
      • MySQL 8.0では同じ名前の sha256_passwordプラグインがコミュニティー版でも使えるようになった。実装まで同じかどうかは知らない。
    • MySQL 8.0の sha256_passwordプラグインと同じものだとすると、パスワードハッシュが $5$から始まる66桁になるので、 Password CHAR(41) NOT NULLには入らずAuthentication_string TEXTに入れることになったのかな
      • passwordカラムの長さを変えるのはリスキーだなって感じで
  • MySQL 8.0では sha256_passwordと同じく256bitのSHA2を使う cache_sha2_passwordがデフォルトになるのもあって、この時点までに passwordカラムがなくなって authentication_stringに一本化されるのは確定だっただろうから、ちょっと前倒ししてMySQL 5.7で消すことにしたんじゃないかなと思う。
動作とかの部分はそれなりに調べたけど、経緯の部分は想像しているだけで裏付けとかはないです。

ER_OPTION_PREVENTS_STATEMENT(Error: 1290) The MySQL server is running with the .. について

$
0
0
$ perror 1290
MySQL error code MY-001290 (ER_OPTION_PREVENTS_STATEMENT): The MySQL server is running with the %s option so it cannot execute this statement
要は、「 %sだからそのSQLは実行できないよ」というエラー。
%sの部分に何が入るかは何パターンかあるけど、基本的にオプションの名前が入るので、SQLを成功させたければ %sをOFFにしてやれば上手くいくはず。

MySQL 5.7.24のコードから ER_OPTION_PREVENTS_STATEMENTを投げるところを引いてみた感じ、あり得る %sのパターンはたぶんこう。

おそらくここまでがよくあるやつら。
ここからはコードをさらって見つけた変なやつら。
  • “—event-scheduler=DISABLED or —skip-grant-tables”
    • これ全部で1区切り
    • 実は event_schedulerON, OFF, DISABLEDの3通りを取ることができて、 DISABLEDで起動すると SET GLOBALでONにできなくなる(それをやろうとするとこれが出る)
    • skip_grant_tablesでもイベントスケジューラーいじれないのね(知らなかった)
  • embedded
    • 組み込み用mysqldである libmysqldを使っている場合に行ベースのバイナリーログイベント( mysqlbinlogでデコードしたものを含む)を食わせると発生
    • 初めて知った…
あー面白かった。
Viewing all 589 articles
Browse latest View live