GA

2018/04/16

MySQL 8.0.3とそれ以降では expire_logs_days は非推奨なパラメーターになりました

TL;DR


MySQL 8.0.1 で導入された binlog_expire_logs_seconds 当初は expire_logs_days足し合わせる という互換性に考慮した 結果余計ややこしい ユニークな設定方法になっていたのですが、 MySQL 8.0.4
  • binlog_expire_logs_secondsが設定されいてる場合はbinlog_expire_logs_secondsのみ適用、expire_logs_daysは無視される
  • binlog_expire_logs_secondsが未設定または0の時のみexpire_logs_daysが適用される
に変わっていた。
一緒に指定しようとするとこんなワーニングになる。
2018-04-16T04:37:43.284839Z 0 [Warning] [MY-011079] The option expire_logs_days cannot be used together with option binlog_expire_logs_seconds. Therefore, value of expire_logs_days is ignored.
ところで、MySQL 8.0.11で binlog_expire_logs_seconds のデフォルトが2592000(30日)に変わるらしいんだけど、この場合binlog_expire_logs_secondsが未設定だとこの値が適用されそうだから、明示的に binlog_expire_logs_seconds=0 にした時だけ expire_logs_days の評価に入るのかしらん、という感じ。
ちなみにMySQL 8.0.4現在では、 binlog_expire_logs_secondsexpire_logs_days は特に連動していない( expire_logs_days の値が有効になっている状態でも binlog_expire_logs_seconds に反映してくれたりはしない)。
飽くまで「どちらか片方だけが設定されているていで、バイナリーログがパージされる関数の中で計算する」だけだった。
というわけで、MySQL 8.0向けの秘伝のタレは expire_logs_daysbinlog_expire_logs_seconds に書き換えておきましょう。
やらかした ありがちなミスとしては、
  • expire_logs_seconds って書いて「そんなパラメーター知らん」って言われる
  • 値を変えるときに 日 -> 秒への変換を忘れてバイナリーログがあっという間にパージされている
くらいでしょうか。お気を付けください。

2018/04/13

MySQL 8.0の再帰CTE(WITH RECURSIVE)で1000行以上の結果セットを作りたいとき

TL;DR


単なる連番のテストデータを作りたい時とか、再帰CTEは便利(というかMySQLerは今まで再帰CTEが使えなかったので、そもそもそれ以外の使い方は思いつかないわけだが)だけれど、 ↓ を訳した時点ではWHERE句を間違えるとさっくりとクエリーが逝きっぱなしになっていた。
それが、MySQL 8.0.3から cte_max_recursion_depth が追加されて、「これを超えるステップの再起CTEはエラー」にするようになっていた。
mysql80 36> SELECT @@session.cte_max_recursion_depth;
+-----------------------------------+
| @@session.cte_max_recursion_depth |
+-----------------------------------+
|                              1000 |
+-----------------------------------+
1 row in set (0.01 sec)

mysql80 36> WITH RECURSIVE t AS (SELECT 1 AS num UNION ALL SELECT num + 1 FROM t WHERE num < 10) SELECT * FROM t;
+------+
| num  |
+------+
|    1 |
|    2 |
|    3 |
|    4 |
|    5 |
|    6 |
|    7 |
|    8 |
|    9 |
|   10 |
+------+
10 rows in set (0.00 sec)

mysql80 36> WITH RECURSIVE t AS (SELECT 1 AS num UNION ALL SELECT num + 1 FROM t WHERE num < 1001) SELECT * FROM t;
ERROR 3636 (HY000): Recursive query aborted after 1001 iterations. Try increasing @@cte_max_recursion_depth to a larger value.
ちなみにこれ系のパラメーターって 0 にセットすると無制限になると思うじゃろ? 何故かこいつは違うんじゃ
mysql80 36> SELECT @@session.cte_max_recursion_depth;
+-----------------------------------+
| @@session.cte_max_recursion_depth |
+-----------------------------------+
|                                 0 |
+-----------------------------------+
1 row in set (0.00 sec)

mysql80 36> WITH RECURSIVE t AS (SELECT 1 AS num UNION ALL SELECT num + 1 FROM t WHERE num < 10) SELECT * FROM t;
ERROR 3636 (HY000): Recursive query aborted after 1 iterations. Try increasing @@cte_max_recursion_depth to a larger value.

mysql80 36> WITH RECURSIVE t AS (SELECT 1 AS num UNION ALL SELECT num + 1 FROM t WHERE num < -1) SELECT * FROM t; -- シードSELECTの1行しか返さないような条件でもアウト
ERROR 3636 (HY000): Recursive query aborted after 1 iterations. Try increasing @@cte_max_recursion_depth to a larger value.
0にすると再帰CTEが一切合切拒否される仕様になっております。
ほわー。

MySQL 8.0は SELECT .. FOR UPDATE SKIP LOCKED とJSON_TABLES関数で「取り敢えずJSON」が捗る?

TL;DR

  • auto_increment + JSON型(あるいはBLOB型やTEXT型でもいいけど)に生のJSONを突っ込む
  • 後から SELECT .. FOR UPDATE SKIP LOCKED でワーカーが取り出して、 INSERT .. SELECT JSON_TABLE(..) FROM .. で正規化したテーブルに突っ込みなおす
  • 外部APIからの戻りのJSONを取り敢えずログテーブルに格納して…みたいな感じを想像している

PoC

こんなテーブルを用意した。
mysql80 13> SHOW CREATE TABLE t2\G
*************************** 1. row ***************************
       Table: t2
Create Table: CREATE TABLE `t2` (
  `seq` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `raw_json` json DEFAULT NULL,
  PRIMARY KEY (`seq`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4  COLLATE=utf8mb4_ja_0900_as_cs
1 row in set (0.00 sec)
Twitterから1~10件のTweetを取ってきて、そのままテーブルに突っ込む。
my $count= int(rand(10) + 1);
my $json= to_json($twitter->search({q => "'MySQL'", lang => "ja", count => $count}));

$conn->do("INSERT INTO t2 (raw_json) VALUES (?)", undef, $json);
何回か叩いてテーブルの中身を確認するとこんな感じ。
mysql80 29> SELECT seq, JSON_LENGTH(raw_json->'$.statuses') FROM t2;
+-----+-------------------------------------+
| seq | JSON_LENGTH(raw_json->'$.statuses') |
+-----+-------------------------------------+
|   1 |                                   7 |
|   2 |                                   8 |
|   3 |                                  10 |
|   4 |                                  10 |
|   5 |                                   8 |
|   6 |                                   1 |
|   7 |                                   6 |
+-----+-------------------------------------+
7 rows in set (0.00 sec)
この状態で別々のクライアントから SELECT .. ORDER BY seq ASC LIMIT 1 FOR UPDATE SKIP LOCKED することで、「今他のクライアントがロックしていない行をseqの若い順に1件ロック」が表現できる。
mysql80 33> BEGIN;
Query OK, 0 rows affected (0.00 sec)

mysql80 33> SELECT seq, raw_json FROM t2 ORDER BY seq ASC LIMIT 1 FOR UPDATE SKIP LOCKED INTO @seq, @raw_json;
Query OK, 1 row affected (0.00 sec)

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

------

mysql80 34> BEGIN;
Query OK, 0 rows affected (0.00 sec)

mysql80 34> SELECT seq, raw_json FROM t2 ORDER BY seq ASC LIMIT 1 FOR UPDATE SKIP LOCKED INTO @seq, @raw_json;
Query OK, 1 row affected (0.00 sec)

mysql80 34> SELECT @seq;
+------+
| @seq |
+------+
|    2 |
+------+
1 row in set (0.00 sec)
それぞれのクライアントで seq=1seq=2 の行をロックするついでに、 @raw_json 変数に raw_json カラムの中身を代入している。
JSON_TABLES関数は残念ながらカラムの値を直接参照できない(びっくりした)ので、一度変数の中に入れてやらないといけない。むむむ。
mysql80 34> SELECT tweet_id, text FROM JSON_TABLE(@raw_json, '$.statuses[*]' COLUMNS (tweet_id NUMERIC(32) PATH '$.id', text VARCHAR(300) PATH '$.text')) AS json;
+--------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| tweet_id           | text                                                                                                                                                                                                                                                 |
+--------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| 984620024198414338 | MySQL派でした                                                                                                                                                                                                                                        |
| 984619948621230080 | MySQLくらいしか触ったことない                                                                                                                                                                                                                        |
| 984616323194896384 | なんか最近mysqlでやべえアプデとかあった?                                                                                                                                                                                                            |
| 984614613839888384 | この前調べてみたらレンタカーよりカーシェアリングの方が安いという事実を知った。そんなに進んでいたのね。。。 #PHP #MySQL                                                                                                                               |
| 984613344014290944 | 速効!図解プログラミングPHP   MySQL―Windows/Linux PHP5対応 PHP5の基本から一歩ずつ学習。MySQLとの連携もマスター。力だめしの練習問題付き https://t.co/oT1wBBVkKV                                                                                      |
| 984608290205085696 | ISBB@東京の案件情報です。動画配信サイトシステム構築@都内。PHP(LAMP環境における開発経験3年以上必須。MySql経験尚可、アジャイル経験尚可。詳細はこちらhttps://t.co/Mtw5EpBQNO                                                                          |
| 984605840580591616 | ををを。。。MySQLのクエリの後に \G ってつけると、縦表示になるのか!初めて知った。

SELECT * FROM `table` LIMIT 10 \G;                                                                                                                                |
| 984603973939150850 | RT @yoku0825: デフォルトのままでもCPUを食い尽くすような正規表現はちゃんとタイムアウトするようになってた。えらい。

日々の覚書: 「危険な正規表現」 vs MySQL 8.0 https://t.co/hNpozpQnWz                                                               |
+--------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
8 rows in set (0.00 sec)
JSON_TABLES は結構書式がややこしくて、FROM (JSON_TABLE(..)) AS dummy のFROM句サブクエリーの形で SELECT に受けるっぽい。
ちょっと思ったより使いにくくてびっくりしたけど、MySQLサーバー側の変数で完結できればJSONをパースするのにクライアントに転送しなくていいので通信量の削減にはなると思う。
ともあれ、 SELECT で受けられれば INSERT INTO .. SELECT .. の形に受けられるので、別のテーブルにINSERTして
mysql80 34> INSERT INTO t3 (tweet_id, text) SELECT tweet_id, text FROM JSON_TABLE(@raw_json, '$.statuses[*]' COLUMNS (tweet_id NUMERIC(32) PATH '$.id', text VARCHAR(300) PATH '$.text')) AS json;
Query OK, 8 rows affected (0.01 sec)
Records: 8  Duplicates: 0  Warnings: 0

mysql80 34> DELETE FROM t2 WHERE seq = @seq;
Query OK, 1 row affected (0.00 sec)

mysql80 34> COMMIT;
Query OK, 0 rows affected (0.00 sec)

mysql80 34> SELECT seq, JSON_LENGTH(raw_json->'$.statuses') FROM t2;
+-----+-------------------------------------+
| seq | JSON_LENGTH(raw_json->'$.statuses') |
+-----+-------------------------------------+
|   1 |                                   7 |
|   3 |                                  10 |
|   4 |                                  10 |
|   5 |                                   8 |
|   6 |                                   1 |
|   7 |                                   6 |
+-----+-------------------------------------+
6 rows in set (0.00 sec)
JSON_TABLES関数がアレなことを除けば 割ときれいにジョブワーカーっぽいことができそうな予感。
ただこれ t2 (取り敢えずJSONで突っ込む)側のロックは分けられるけど、 t3 (正規化して突っ込む先)側はフツーにネクストキーロックとかデッドロックとかのアレを食らうので、 t3 側のロックはちゃんと設計しないと死ねそう。

2018/04/12

「危険な正規表現」 vs MySQL 8.0

他にもいくつかあると思うけれど、俺の一番のお気に入り(?)はこれ。
(.*)*^
試してみよう。
mysql80 15> SELECT * FROM t1 LIMIT 3;
+-------------+---------------------+------------------------------------------------------------+--------------------------------------------------------------------+
| tweet_id    | timestamp           | source                                                     | text                                                               |
+-------------+---------------------+------------------------------------------------------------+--------------------------------------------------------------------+
| 22431873995 | 2010-08-29 09:00:00 | <a href="http://twicca.r246.jp/" rel="nofollow">twicca</a> | 明日一年ぶり夜勤。                                                 |
| 22575355920 | 2010-08-31 09:00:00 | <a href="http://twicca.r246.jp/" rel="nofollow">twicca</a> | 夜勤明けの電車、なんでこんなに人い るんだ。。                       |
| 22628108281 | 2010-08-31 09:00:00 | <a href="http://twicca.r246.jp/" rel="nofollow">twicca</a> | androidにunix likeな機能求める方が 。。か?                         |
+-------------+---------------------+------------------------------------------------------------+--------------------------------------------------------------------+
3 rows in set (0.00 sec)

mysql80 15> SELECT COUNT(*) FROM t1;
+----------+
| COUNT(*) |
+----------+
|    41123 |
+----------+
1 row in set (0.02 sec)
俺のTweet履歴を食わせたこんなテーブルに対して
mysql80 17> SELECT COUNT(*) FROM t1 WHERE text RLIKE '(.*)*^';
ERROR 3700 (HY000): Timeout exceeded in regular expression match.
サクッとタイムアウトが起こった。3700番なので新しいヤーツ。
このタイムアウトは1回の正規表現の評価が regexp_time_limit で設定された値を超えると出るヤーツ。単位は およそ ミリ秒。デフォルト32ミリ秒。
mysql80 17> SHOW VARIABLES LIKE '%reg%';
+--------------------+---------+
| Variable_name      | Value   |
+--------------------+---------+
| regexp_stack_limit | 8000000 |
| regexp_time_limit  | 32      |
+--------------------+---------+
2 rows in set (0.01 sec)
なんで およそ なんて珍しい枕詞がついてるかというと、オリジナルのICUのドキュメントにもそう書いてあるから。
mysql80 17> WITH t AS (SELECT REPEAT('a', 16) AS a) SELECT * FROM t WHERE a RLIKE '(.*)*^';
+------------------+
| a                |
+------------------+
| aaaaaaaaaaaaaaaa |
+------------------+
1 row in set (0.03 sec)

mysql80 17> WITH t AS (SELECT REPEAT('a', 17) AS a) SELECT * FROM t WHERE a RLIKE '(.*)*^';
ERROR 3700 (HY000): Timeout exceeded in regular expression match.
16文字で0.03sec = 30ms前後ってことはこれCPUぶん回したらタイムアウトするようになるかな? と思ってぶん回してみたけど、フツーに60ms前後かかってもタイムアウトする様子はない。何故だろ。
mysql80 18> WITH t AS (SELECT REPEAT('a', 16) AS a) SELECT * FROM t WHERE a RLIKE '(.*)*^';
+------------------+
| a                |
+------------------+
| aaaaaaaaaaaaaaaa |
+------------------+
1 row in set (0.06 sec)
regexp_time_limit=0 にするとタイムアウトが無効になるので、この正規表現で線形に時間を食われていく様がまざまざと観測できる。1行でこれなので、複数行これを評価させれば更に倍々ゲームで逝くことになるので、0にすることはないだろうなぁ…。
ちなみにセッション値を持たない、グローバル値のみなので SET GLOBAL でやらないといけない。
mysql80 18> SET GLOBAL regexp_time_limit= 0;
Query OK, 0 rows affected (0.00 sec)

mysql80 18> WITH t AS (SELECT REPEAT('a', 17) AS a) SELECT * FROM t WHERE a RLIKE '(.*)*^';
+-------------------+
| a                 |
+-------------------+
| aaaaaaaaaaaaaaaaa |
+-------------------+
1 row in set (0.02 sec)

mysql80 18> WITH t AS (SELECT REPEAT('a', 18) AS a) SELECT * FROM t WHERE a RLIKE '(.*)*^';
+--------------------+
| a                  |
+--------------------+
| aaaaaaaaaaaaaaaaaa |
+--------------------+
1 row in set (0.04 sec)

mysql80 18> WITH t AS (SELECT REPEAT('a', 19) AS a) SELECT * FROM t WHERE a RLIKE '(.*)*^';
+---------------------+
| a                   |
+---------------------+
| aaaaaaaaaaaaaaaaaaa |
+---------------------+
1 row in set (0.08 sec)

..

mysql80 18> WITH t AS (SELECT REPEAT('a', 26) AS a) SELECT * FROM t WHERE a RLIKE '(.*)*^';
+----------------------------+
| a                          |
+----------------------------+
| aaaaaaaaaaaaaaaaaaaaaaaaaa |
+----------------------------+
1 row in set (11.31 sec)
あとちなみに、 regexp_time_limit なんて設定できなかったMySQL 5.7とそれ以前でこれをやるとどうなるかというと
mysql57 8> SELECT * FROM (SELECT REPEAT('a', 26) AS a) AS t WHERE a RLIKE  '(.*)*^';
+----------------------------+
| a                          |
+----------------------------+
| aaaaaaaaaaaaaaaaaaaaaaaaaa |
+----------------------------+
1 row in set (0.00 sec)

mysql57 8> SELECT * FROM (SELECT REPEAT('a', 32) AS a) AS t WHERE a RLIKE  '(.*)*^';
+----------------------------------+
| a                                |
+----------------------------------+
| aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa |
+----------------------------------+
1 row in set (0.00 sec)
一瞬で帰ってくる。これはMySQL 5.7とそれ以前の正規表現は繰り返しマッチをしないからだと思う。

まま、デフォルトの regexp_time_limit はそれなりに良い値なのではないかと思う。

2018/03/30

wslbridgeを使ってCygtermからWSLを扱うときに ALT + Dとかがアレになるやつの対策

/etc/bash.bashrc に↓を書き加えてもう二度とCygwin bashをまともに起動しないことにした。

exec /cygdrive/c/Users/yoku0825/wslbridge-0.2.4-cygwin64/wslbridge.exe -C ~

2018/03/27

#MANABIYA に行ってきました

3/23に開催された MANABIYA に行ってきました。
1日目の3/23しか行けなかったけれどかなり豪華な コンテンツ で、 澤田さん のPostgreSQLの今とこれから、 自分のセッション 、間に職員室なるものをはさんで 澤田さん, 木下さん , そーだいさん と謎のCrossSession、 kazeburoさんマイクロサービス on マルチクラウド と詰め込んだ1日でした。
モリスさん分散処理とコンテナ化インフラの面白い関係 も聞きたかったしJavaのカルチャーとグロースもすごく評判が良かったけれど自分のセッションとカブったのだ…。
どのセッションを聞こうかと悩むのはマルチトラックの楽しいところでもあるけれど、しゃべる側としては悩ましいところでもあり。

わたしのセッション コミュニティー版ユーザーの視点からMySQLのこれまでを振り返る ではなんかゆるくMySQLのこれまでの機能追加についてなぞってみました。




改めて振り返ってみると、MySQLってここ数年で随分便利になったんですね。花マルをあげたい。

2018/03/12

#phperkaigi 2018 がとても楽しかった

PHPerKaigi 2018 に参加してきました。
まずは スポンサー のみなさん、いろいろ美味しゅうございましたありがとうございました。 スタッフ のみなさん、いろいろフォローありがとうございました。特に @uessy_akr さんがセッションの前後色々話しかけたりフォローしたりしてくれて助かりました。ありがとうございました!!
あとは初めて物理 @cakephper さんに会えました。実物は @soudai1025 とちゃんと区別がつきました。青くなかった!

PHPerKaigiのすごかったところ。
  • ラウンジに “Ask the speaker” というテーブルがあって、しゃべったあとのスピーカーはそこに誘導される
    • 登壇後ぼっちスキルの高い俺でも話しかけてもらえた! 母さん! 今夜(?)は赤飯だ!
  • LT開始時に既に人間をダメにするドリンクが配布されている
    • わかってらっしゃる…
    • どうやら前夜祭からそうだったらしい
      • 参加したかった

  • 安定のアンカンファレンス職人 @soudai1025 さんとキャッキャウフフ
    • アンカンファレンススペースはやっぱり素敵ですね(ただし俺一人だとぼっちになる)
    • @soudai1025 ++ => soudai1026?





  • 人生初 #phpstudy 参加
  • 豪華すぎるビール
    • わかってらっしゃる…!!1
    • ビールスポンサーのDMM.comラボさんありがとうございます…!


わたしのセッションは「サーバーが完膚なきまでに死んでもMySQLのデータを失わないための表技」でした。







SlideShareがスライド修正できなくなってもう一度消してアップロードはあまりにもアレだから…もうSlideShareは使えない(つд`)

スライド中にも言及がありますが、 お題箱 の障害報告を読んでいて(最初は ConoHa だったのか、という文脈で回ってきたのですよ) 「みんなにもっとバックアップの方法を知ってもらった方が良いのでは!」とCfPにしました。
くどいようですが、お題箱がMySQLを使っていたかどうかは知りません。ごめんなさい。これで少しでも世界からバックアップが上手く取れてないケースが減るといいな!
大変楽しかったです。 来年も開催されることを期待してます!