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

MySQL 8.0.4でMySQLの正規表現演算がだいぶマシになる

$
0
0

TL;DR

  • MySQL 5.7とそれ以前にも一応 REGEXP演算子(またはRLIKE演算子)はあって、多少正規表現っぽいことはできるんだけど正規表現としては全然物足りなかった。
    • 少なくとも \sで空白文字にマッチできないとかちょっとPerlの正規表現で甘やかされた身にとってはつらい
    • しかも遅いんだこれが
    • あとマルチバイト非対応(マルチバイトに対して使おうと思ったことないけど)
  • MySQL 8.0.4とそれ以降ではICUの正規表現エンジンを使うことでかなーりマシに。

古くからこういう使い方はできた。
カラム名 REGEXP '正規表現文字列'(俺はRLIKEの方が好きでRLIKEって書くけどREGEXP演算子と意味は一緒、カラム名で使うことが多いけど正しくは判定文字列)
mysql57 5> SELECT * FROM t1 WHERE text RLIKE '^MySQL 5.[67]' LIMIT 3;
+--------------------+---------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| tweet_id | timestamp | text |
+--------------------+---------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| 252971369963335681 | 2012-10-02 12:20:49 | MySQL 5.6.7のチェンジログにしっかり見つけた。。 http://t.co/fR5yc3vD |
| 342484389072093184 | 2013-06-06 12:33:36 | MySQL 5.6.12 has still this bugってPHPから叩くときのこと?

MySQL Bugs: #69027: Default secure_auth value breaking PHP connects http://t.co/yx2f54Viwp |
| 343936149896720384 | 2013-06-10 12:42:22 | MySQL 5.6 root ログインできない っていう検索トラフィック増えてきてるんですけど。 。orz |
+--------------------+---------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------+
3 rows in set (0.01 sec)

mysql57 5> SELECT * FROM t1 WHERE text RLIKE 'MySQL\\sEnterprise' LIMIT 3;
Empty set (0.51 sec)

mysql57 5> SELECT * FROM t1 WHERE text RLIKE '^マイ[エ]ス' LIMIT 3;
Empty set (0.06 sec)
新しい正規表現は REGEXP_LIKE関数で提供されるけれど、今までどおりのRLIKE, REGEXP演算子を使った書き方もできる。その場合でも使う正規表現エンジンはICU版。
mysql80 13>SELECT * FROM t1 WHERE text RLIKE '^MySQL 5.[67]' LIMIT 3;
+--------------------+---------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| tweet_id | timestamp | text |
+--------------------+---------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| 252971369963335681 | 2012-10-02 12:20:49 | MySQL 5.6.7のチェンジログにしっかり見つけた。。 http://t.co/fR5yc3vD |
| 342484389072093184 | 2013-06-06 12:33:36 | MySQL 5.6.12 has still this bugってPHPから叩くときのこと?

MySQL Bugs: #69027: Default secure_auth value breaking PHP connects http://t.co/yx2f54Viwp |
| 343936149896720384 | 2013-06-10 12:42:22 | MySQL 5.6 root ログインできない っていう検索トラフィック増えてきてるんですけど。 。orz |
+--------------------+---------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------+
3 rows in set (0.01 sec)

mysql80 14> SELECT * FROM t1 WHERE text RLIKE 'MySQL\\sEnterprise' LIMIT 3;
+--------------------+---------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| tweet_id | timestamp | text |
+--------------------+---------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| 366900653253070848 | 2013-08-12 21:35:07 | RT @mysql_japan: MySQL Workbench 6.0がGAになりました。UIのデザインが変更となった ほか、新たにMySQL Enterprise Editionの機能のUIも加わっています。 http://t.co/aiQM21dPZ5 #MySQL #my… |
| 385784283853365249 | 2013-10-04 00:11:55 | MySQL Enterprise Monitorのバグレポート一気に増えてるねぇ。 |
| 404150382252670976 | 2013-11-23 16:32:14 | RT @h141gm: バックアップの方法
mysqldump
コールドバックアップ
スナップショット(→要リカバリ)
バイナリログ
MySQL Enterprise Backup (差分取得も可)
レプリケーション利用
#ost2013 |
+--------------------+---------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
3 rows in set (0.04 sec)

mysql80 13> SELECT * FROM t1 WHERE text RLIKE '^マイ[エ]ス' LIMIT 3;
+--------------------+---------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------+
| tweet_id | timestamp | text |
+--------------------+---------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------+
| 372177762645053440 | 2013-08-27 11:04:28 | マイエスキューエる。 |
| 563640699841560576 | 2015-02-06 19:09:49 | マイエス☆キューエル |
| 747379558760931328 | 2016-06-27 19:42:27 | マイエスキューエルファブリック。是非、声に出していただきたい。なんか響きがかっこ いい気がしてくるから。 |
+--------------------+---------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------+
3 rows in set (0.05 sec)
いいねいいね。
RLIKE演算子ではなくて REGEXP_LIKE(カラム名, '正規表現文字列', 'マッチタイプ')を使うとIgnore Caseができるようになる。マッチタイプは省略可能。
mysql80 15> SELECT * FROM t1 WHERE REGEXP_LIKE(text, 'mysql', 'i') LIMIT 3;
+--------------------+---------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| tweet_id | timestamp | text |
+--------------------+---------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| 167925415959199744 | 2012-02-10 19:58:33 | myspiをmysqlに空目… RT @lifehackerjapan: 最新記事: あごを脱力して心の中で「アー」と言う!? 薬なしでも不眠を克服できる簡単なテクニック #myspi http://t.co/6pueAVSf #lh_jp |
| 188134150971203586 | 2012-04-06 14:20:51 | MySQL DBA試験に向けて追い込み。 |
| 188280387879968768 | 2012-04-07 00:01:57 | MySQL DBA受検の為にLinuxにもMySQL入れとこう⇒VirtualBox使ってCentOS入れる(2日)⇒FEDERATEDとNDB使いたいからソースからコンパイルしよう(1日)⇒試験まであと3日(昨日の夜ここ) |
+--------------------+---------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
3 rows in set (0.00 sec)
OLTPの中で使うことはないだろうけど、ちょっとした飛び道具にRLIKEはそこそこ好きなのでちょっとだけ嬉しい。


【2018/01/30 16:54】
なお、ドキュメントにはしゃらっと書かれているけれど

Because MySQL uses the C escape syntax in strings (for example, \n to represent the newline character), you must double any \ that you use in your expr and pat arguments.

なので、 '\s'をREGEXP_LIKEに渡す時は '\\s'にしてやらないといけない


MySQL 8.0.4の正規表現で更に遊ぶ(REGEXP_SUBSTR, REGEXP_INSTR)

$
0
0

MySQL 8.0.4で新たに追加された関数として、 REGEXP_SUBSTRREGEXP_INSTRがある。
REGEXP_REPLACEもあるけどこれはいいや( mroonga_snippet的なことができるかもなのでまた別で遊ぶかも)
検索する正規表現にマッチした文字列を返してくれるREGEXP_SUBSTRとその文字列が現れるオフセットを返してくれるREGEXP_INSTR。
特にREGEXP_SUBSTRは面白そうなんだけど、引数が (expr, pat[, pos[, occurrence[, match_type]]])になっている時点で複数回マッチしたものを配列で受けるなんてやり方はできない。残念…。
という訳で、折角MySQL 8.0なのでCTEを使ってこれを受け取れるんじゃないかテスト。
mysql80 19> SET @pattern := 'MySQL\\s*\\d[\.\\d]+';
Query OK, 0 rows affected (0.00 sec)

mysql80 19> WITH RECURSIVE cte (step, tweet_id, text, str, pos) AS (
-> SELECT 1 AS step, tweet_id, text, CAST(REGEXP_SUBSTR(text, @pattern, 1, 1) AS char(1024)) AS str, REGEXP_INSTR(text, @pattern, 1, 1) FROM t1 WHERE REGEXP_INSTR(text, @pattern, 1, 1)
-> UNION ALL
-> SELECT step + 1, tweet_id, text, CAST(REGEXP_SUBSTR(text, @pattern, 1, step + 1) AS char(1024)), REGEXP_INSTR(text, @pattern, 1, step + 1) FROM cte WHERE REGEXP_INSTR(text, @pattern, 1, step + 1) )
->
-> SELECT tweet_id, ANY_VALUE(text), GROUP_CONCAT(str), GROUP_CONCAT(pos) FROM cte GROUP BY tweet_id HAVING COUNT(*) > 1 ORDER BY tweet_id LIMIT 5\G
*************************** 1. row ***************************
tweet_id: 219335904735657984
ANY_VALUE(text): RT @sh2nd: MySQL 5.1はInnoDBの管理スレッドがos_thread_sleep(1000000);していて、MySQL 5.5はos_event_wait_time_low(srv_timeout_event, 1000000, sig_count) ...
GROUP_CONCAT(str): MySQL 5.1,MySQL 5.5
GROUP_CONCAT(pos): 12,66
*************************** 2. row ***************************
tweet_id: 255543243696136192
ANY_VALUE(text): 最近Vadimさん、MySQL5.6にお熱で嬉しい。

MySQL 5.6.7-RC in tpcc-mysql benchmark http://t.co/aBUKFgaL
GROUP_CONCAT(str): MySQL5.6,MySQL 5.6.7
GROUP_CONCAT(pos): 11,30
*************************** 3. row ***************************
tweet_id: 294125457853513729
ANY_VALUE(text): RT @i_rethi: ぐぐったらあんまりtcmallocをMySQL5.5で使うケースが日本語では見当たらなかったので書いてみた / MySQL5.5でtcmallocを使用する http://t.co/GuatemFS
GROUP_CONCAT(str): MySQL5.5,MySQL5.5
GROUP_CONCAT(pos): 32,70
*************************** 4. row ***************************
tweet_id: 298969186275844097
ANY_VALUE(text): RT @nippondanji: ブログ書きました: MySQL 5.6正式リリース!! #mysql56 http://t.co/zGSh0ArK
GROUP_CONCAT(str): MySQL 5.6,mysql56
GROUP_CONCAT(pos): 29,48
*************************** 5. row ***************************
tweet_id: 301220291202412544
ANY_VALUE(text): ひょひょっと調べたので書いてみた。

日々の覚書: MySQL5.6のマスターにMySQL5.5(とそれ以前)のスレーブをぶら下げるとエラる http://t.co/kIjOmBdd
GROUP_CONCAT(str): MySQL5.6,MySQL5.5
GROUP_CONCAT(pos): 28,42
5 rows in set (0.17 sec)
textが MySQL\\s*\\d[\.\\d]+にマッチするもの(MySQL バージョン番号っぽい文字列)のうち、2回以上マッチしそうなもの( SELECT .. FROM cte .. HAVING COUNT(*) > 1)を引っこ抜いてみる。
WITH RECURSIVEで再帰CTEにして、引数の occurrence (=何番目のマッチ) をインクリメントさせている。
有用かどうかは置いておいてMySQLでもこういうことができるようになったかとちょっと感慨深い。

LOAD DATA INFILEステートメントの中でカラムの順番とかをゴニョる

$
0
0

TL;DR

  • LOAD DATA INFILEステートメントで、CSVなりTSVなりのフィールドの並び順とテーブルのカラムの並び順が一緒じゃない時にほげる方法とか
  • 読み取った値を加工してからテーブルに突っ込む方法とか

Twitterからダウンロードできる tweets.csvはこんなフォーマットをしている。
"tweet_id","in_reply_to_status_id","in_reply_to_user_id","timestamp","source","text","retweeted_status_id","retweeted_status_user_id","retweeted_status_timestamp","expanded_urls"
"878118626489802752","","","2017-06-23 05:12:49 +0000","<a href=""http://twitter.softama.com/"" rel=""nofollow"">ツイタマ+ for Android</a>","宇宙の 法則が 乱れる!","","","",""
先頭1行がヘッダ行になっていて、フィールドは全てダブルクォートされ、タイムスタンプはUTCで、UTCであることが +0000でわかるようになっている(聞こえますかMySQLのみなさん)
ちなみに expanded_urlsが複数ある場合、特に断りもなくコンマ区切りで後ろに要素が続く(10フィールドより多くフィールドがある場合がある、ばかな)
これをこんな感じのテーブルに、MySQLだけでパースシテ何とか上手いことLOAD DATA INFILEしたいとする。
mysql80 23> SHOW CREATE TABLE t1\G
*************************** 1. row ***************************
Table: t1
Create Table: CREATE TABLE `t1` (
`tweet_id` bigint(20) unsigned NOT NULL,
`timestamp` datetime NOT NULL,
`source` text COLLATE utf8mb4_ja_0900_as_cs NOT NULL,
`text` text COLLATE utf8mb4_ja_0900_as_cs NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_ja_0900_as_cs
1 row in set (0.01 sec)
まずはわかりやすくするために、全フィールドの内容を変数に受け取るステートメント。
mysql80 23> LOAD DATA
-> INFILE '/home/yoku0825/tweets.csv'
-> INTO TABLE t1
-> FIELDS TERMINATED BY ','
-> ENCLOSED BY '"'
-> IGNORE 1 LINES
-> (@tweet_id,
-> @in_reply_to_status_id,
-> @in_reply_to_user_id,
-> @timestamp,
-> @source,
-> @text,
-> @retweeted_status_id,
-> @retweeted_status_user_id,
-> @retweeted_status_timestamp,
-> @expanded_urls)
-> ;
Query OK, 41123 rows affected (0.50 sec)
Records: 41123 Deleted: 0 Skipped: 0 Warnings: 0
FILEDSとかIGNOREとかのフォーマット指定の 後に (いつも忘れる) 、変数のリストを並べることで、それぞれのフィールドをそれぞれの変数に受け取ることができる。
(↑のLOAD DATA INFILEは変数にセットするだけでカラムに何も値をセットしていないので、恐ろしく空っぽな行がt1テーブルにロードされている…)
この変数を使って、SET句でカラムに値を指定することができる。
mysql80 23> TRUNCATE t1;
Query OK, 0 rows affected (0.09 sec)

mysql80 25> LOAD DATA
-> INFILE '/home/yoku0825/tweets.csv'
-> INTO TABLE t1
-> FIELDS TERMINATED BY ','
-> ENCLOSED BY '"'
-> IGNORE 1 LINES
-> (@tweet_id,
-> @in_reply_to_status_id,
-> @in_reply_to_user_id,
-> @timestamp,
-> @source,
-> @text,
-> @retweeted_status_id,
-> @retweeted_status_user_id,
-> @retweeted_status_timestamp,
-> @expanded_urls)
-> SET
-> tweet_id = @tweet_id,
-> timestamp = TIMESTAMPADD(HOUR, 9, REGEXP_SUBSTR(@timestamp, '\\d{4}-\\d{2}-\\d{2}\\s+\\d{2}:\\d{2}:\\d{2}')),
-> source = @source,
-> text = @text
-> ;
Query OK, 41123 rows affected (1.33 sec)
Records: 41123 Deleted: 0 Skipped: 0 Warnings: 0
折角なので REGEXP_SUBSTRで遊んでみた時のを活かして、タイムスタンプを正規表現でゴニョっている。
基本的な仕組みはこんなところで、余計なフィールドを変数で受けずに捨てるための変数( @dummy )に受けたりカラムに直接渡したりなんてことをすると最終的に
mysql80 25> TRUNCATE t1;
Query OK, 0 rows affected (0.04 sec)

mysql80 25>
mysql80 25>
mysql80 25> LOAD DATA
-> INFILE '/home/yoku0825/tweets.csv'
-> INTO TABLE t1
-> FIELDS TERMINATED BY ','
-> ENCLOSED BY '"'
-> IGNORE 1 LINES
-> (tweet_id,
-> @dummy,
-> @dummy,
-> @timestamp,
-> source,
-> text,
-> @dummy,
-> @dummy,
-> @dummy,
-> @dummy
-> )
-> SET
-> timestamp = TIMESTAMPADD(HOUR, 9, REGEXP_SUBSTR(@timestamp, '\\d{4}-\\d{2}-\\d{2}\\s+\\d{2}:\\d{2}:\\d{2}'))
-> ;
Query OK, 41123 rows affected (1.05 sec)
Records: 41123 Deleted: 0 Skipped: 0 Warnings: 0

mysql80 25> SELECT * FROM t1 LIMIT 3;
+--------------------+---------------------+--------------------------------------------------------------------------------------+--------------------------------------------------------------------------------+
| tweet_id | timestamp | source | text |
+--------------------+---------------------+--------------------------------------------------------------------------------------+--------------------------------------------------------------------------------+
| 878118626489802752 | 2017-06-23 14:12:49 | <a href="http://twitter.softama.com/" rel="nofollow">ツイタマ+ for Android</a> | 宇宙の 法則が 乱れる! |
| 878116753091448832 | 2017-06-23 14:05:23 | <a href="http://twitter.com/download/android" rel="nofollow">Twitter for Android</a> | RT @xaicron: @yoku0825 MySQLは時を超える... |
| 878116497389797377 | 2017-06-23 14:04:22 | <a href="http://twitter.softama.com/" rel="nofollow">ツイタマ+ for Android</a> | @xaicron ( д ) ゚ ゚ ホントだ!! ありがとうございます! |
+--------------------+---------------------+--------------------------------------------------------------------------------------+--------------------------------------------------------------------------------+
3 rows in set (0.00 sec)
こんな感じになる。


って感じでどうでしょう? :)



MySQL Router 2.1.5経由でのMySQLへの接続に失敗する

$
0
0

TL;DR


ことの起こりは単なるError: 2013(実際はコマンドラインクライアントじゃなくて、 レプリケーションのI/Oスレッドが起こしたんだけど
$ mysql -h127.0.0.1 -P13306
ERROR 2013 (HY000): Lost connection to MySQL server at 'reading initial communication packet', system error: 0

$ sudo less /var/log/mysqlrouter/mysqlrouter.log
2018-02-21 12:27:25 INFO [7fe221c14700] [routing:test] started: listening on 127.0.0.1:13306; read-write
2018-02-21 12:27:39 WARNING [7fe213fff700] Timeout reached trying to connect to MySQL Server xxxx:3306: Connection timed out
2018-02-21 12:27:48 INFO [7fe213fff700] [routing:test] fd=5 Pre-auth socket failure 127.0.0.1: client auth timed out
こればっかり出て、destinationに全然つながらなくなった。
隣の同僚こと 角煮の深町が「2.1.4では問題なくて2.1.5にバージョンアップしてから発生する」というところまで切り分けてくれたので、そのへんを重点的に調べることに。
取り敢えずリリースノートにはそんな変なことは書いてない。
とはいえタイムアウトって書いてあるからタイムアウトなんだろうけどなんか変わったのかな? って調べたらごっついコミットが出てきた。
selectから pollにまるっと書き換わってる。だが8か月前。だったら2.1.5関係ないかと思ったら、2.1.4が2017/07のリリースだから8か月前でも2.1.5が初出だった。マジか。
で、 バグレポートに書いたとおり、以前は selectのタイムアウト timeval型の tv_secにそのまま connect_timeoutを渡していたのでタイムアウトまでの単位は秒だったが、 pollのタイムアウト int型のtimeout_msに connect_timeout1000倍しなければならないのにそれをせずにそのまま渡しているので、タイムアウトまでの単位がミリ秒になってしまった。
mysqlrouter.conf の [routing:xx]セクションに明示的に connect_timeout = 今までの値の1000倍を書けば取り敢えず回避可能なので踏み抜いた際にはお試しください。

MySQL 8.0.4の SHOW GRANTS の結果が想像したのとちょっと違う

$
0
0
MySQL 8.0.4にroot@localhostでログインして SHOW GRANTSを実行したらこうなった。
mysql> SHOW GRANTS;
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Grants for root@localhost |
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, RELOAD, SHUTDOWN, PROCESS, FILE, REFERENCES, INDEX, ALTER, SHOW DATABASES, SUPER, CREATE TEMPORARY TABLES, LOCK TABLES, EXECUTE, REPLICATION SLAVE, REPLICATION CLIENT, CREATE VIEW, SHOW VIEW, CREATE ROUTINE, ALTER ROUTINE, CREATE USER, EVENT, TRIGGER, CREATE TABLESPACE, CREATE ROLE, DROP ROLE ON *.* TO `root`@`localhost` WITH GRANT OPTION |
| GRANT BACKUP_ADMIN,BINLOG_ADMIN,CONNECTION_ADMIN,ENCRYPTION_KEY_ADMIN,GROUP_REPLICATION_ADMIN,PERSIST_RO_VARIABLES_ADMIN,REPLICATION_SLAVE_ADMIN,RESOURCE_GROUP_ADMIN,RESOURCE_GROUP_USER,ROLE_ADMIN,SET_USER_ID,SYSTEM_VARIABLES_ADMIN,XA_RECOVER_ADMIN ON *.* TO `root`@`localhost` WITH GRANT OPTION |
| GRANT PROXY ON ''@'' TO 'root'@'localhost' WITH GRANT OPTION |
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
3 rows in set (0.00 sec)
よく訓練されたMySQLerにはお分かりいただけると思うが、MySQL 5.7とそれ以前であればこんな感じだった。
mysql> SHOW GRANTS;
+---------------------------------------------------------------------+
| Grants for root@localhost |
+---------------------------------------------------------------------+
| GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' WITH GRANT OPTION |
| GRANT PROXY ON ''@'' TO 'root'@'localhost' WITH GRANT OPTION |
+---------------------------------------------------------------------+
2 rows in set (0.00 sec)
ALL修飾子(と呼ぶのか?)がなくなって、更に、SUPER権限の代替になる(らしい) *_ADMIN*的なやつら(ADMINって入ってないやつもあるけど)が列挙されている。
見た目はかなり違うけど、与えられている権限自体は変わらない(そりゃそうだ)
テーブル的には mysql.user(今までと同じ)と mysql.global_grants(SUPER権限を分割したやつら)にそれぞれレコードがあった。
mysql80 7> SELECT * FROM mysql.user WHERE user= 'root'\G
*************************** 1. row ***************************
Host: localhost
User: root
Select_priv: Y
Insert_priv: Y
Update_priv: Y
Delete_priv: Y
Create_priv: Y
Drop_priv: Y
Reload_priv: Y
Shutdown_priv: Y
Process_priv: Y
File_priv: Y
Grant_priv: Y
References_priv: Y
Index_priv: Y
Alter_priv: Y
Show_db_priv: Y
Super_priv: Y
Create_tmp_table_priv: Y
Lock_tables_priv: Y
Execute_priv: Y
Repl_slave_priv: Y
Repl_client_priv: Y
Create_view_priv: Y
Show_view_priv: Y
Create_routine_priv: Y
Alter_routine_priv: Y
Create_user_priv: Y
Event_priv: Y
Trigger_priv: Y
Create_tablespace_priv: Y
ssl_type:
ssl_cipher:
x509_issuer:
x509_subject:
max_questions: 0
max_updates: 0
max_connections: 0
max_user_connections: 0
plugin: caching_sha2_password
authentication_string:
password_expired: N
password_last_changed: 2018-01-30 11:01:30
password_lifetime: NULL
account_locked: N
Create_role_priv: Y
Drop_role_priv: Y
Password_reuse_history: NULL
Password_reuse_time: NULL
1 row in set (0.00 sec)

mysql80 7> SELECT * FROM mysql.global_grants\G
*************************** 1. row ***************************
USER: root
HOST: localhost
PRIV: BACKUP_ADMIN
WITH_GRANT_OPTION: Y
*************************** 2. row ***************************
USER: root
HOST: localhost
PRIV: BINLOG_ADMIN
WITH_GRANT_OPTION: Y
*************************** 3. row ***************************
USER: root
HOST: localhost
PRIV: CONNECTION_ADMIN
WITH_GRANT_OPTION: Y
*************************** 4. row ***************************
USER: root
HOST: localhost
PRIV: ENCRYPTION_KEY_ADMIN
WITH_GRANT_OPTION: Y
*************************** 5. row ***************************
USER: root
HOST: localhost
PRIV: GROUP_REPLICATION_ADMIN
WITH_GRANT_OPTION: Y
*************************** 6. row ***************************
USER: root
HOST: localhost
PRIV: PERSIST_RO_VARIABLES_ADMIN
WITH_GRANT_OPTION: Y
*************************** 7. row ***************************
USER: root
HOST: localhost
PRIV: REPLICATION_SLAVE_ADMIN
WITH_GRANT_OPTION: Y
*************************** 8. row ***************************
USER: root
HOST: localhost
PRIV: RESOURCE_GROUP_ADMIN
WITH_GRANT_OPTION: Y
*************************** 9. row ***************************
USER: root
HOST: localhost
PRIV: RESOURCE_GROUP_USER
WITH_GRANT_OPTION: Y
*************************** 10. row ***************************
USER: root
HOST: localhost
PRIV: ROLE_ADMIN
WITH_GRANT_OPTION: Y
*************************** 11. row ***************************
USER: root
HOST: localhost
PRIV: SET_USER_ID
WITH_GRANT_OPTION: Y
*************************** 12. row ***************************
USER: root
HOST: localhost
PRIV: SYSTEM_VARIABLES_ADMIN
WITH_GRANT_OPTION: Y
*************************** 13. row ***************************
USER: root
HOST: localhost
PRIV: XA_RECOVER_ADMIN
WITH_GRANT_OPTION: Y
13 rows in set (0.00 sec)
ちなみに、 GRANT SUPERしようとしたらワーニングになった。
mysql80 7> GRANT SUPER ON *.* TO root@127.0.0.1;
Query OK, 0 rows affected, 1 warning (0.01 sec)

mysql80 7> SHOW WARNINGS;
+---------+------+----------------------------------------------+
| Level | Code | Message |
+---------+------+----------------------------------------------+
| Warning | 1287 | The SUPER privilege identifier is deprecated |
+---------+------+----------------------------------------------+
1 row in set (0.00 sec)

mysql80 7> SELECT * FROM global_grants WHERE user= 'root' AND host= '127.0.0.1'\G
Empty set (0.00 sec)

mysql80 7> SHOW GRANTS FOR root@127.0.0.1;
+------------------------------------------+
| Grants for root@127.0.0.1 |
+------------------------------------------+
| GRANT SUPER ON *.* TO `root`@`127.0.0.1` |
+------------------------------------------+
1 row in set (0.00 sec)
まだSUPER権限でも管理系の権限は振るえる(今後はわからないけれども9.0かしらね)
mysql80 9> SHOW GRANTS;
+------------------------------------------+
| Grants for root@127.0.0.1 |
+------------------------------------------+
| GRANT SUPER ON *.* TO `root`@`127.0.0.1` |
+------------------------------------------+
1 row in set (0.00 sec)

mysql80 9> SHOW MASTER LOGS;
+------------+-----------+
| Log_name | File_size |
+------------+-----------+
| bin.000001 | 170 |
| bin.000002 | 39229419 |
| bin.000003 | 854 |
| bin.000004 | 791 |
+------------+-----------+
4 rows in set (0.01 sec)

mysql80 9> SET GLOBAL max_connections= 100;
Query OK, 0 rows affected (0.00 sec)
んー、変わっていくなあ。

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

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

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

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





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


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





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

#MANABIYA に行ってきました

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

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




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

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

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

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

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

$
0
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 はそれなりに良い値なのではないかと思う。

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

$
0
0

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側のロックはちゃんと設計しないと死ねそう。

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

$
0
0

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.3とそれ以降では expire_logs_days は非推奨なパラメーターになりました

$
0
0

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って書いて「そんなパラメーター知らん」って言われる
  • 値を変えるときに 日 -> 秒への変換を忘れてバイナリーログがあっという間にパージされている
くらいでしょうか。お気を付けください。

MySQL 8.0のSTATEMENT_DIGEST関数を使ってストアドプロシージャでSQLにホワイトリストを適用する

$
0
0
STATEMENT_DIGEST関数はSQLステートメントから定数をノーマライズしたもの(ダイジェスト)をハッシュ化して返してくれる関数。
MySQL 5.6とそれ以降の performance_schema.events_statements_summary_by_digestなんかで使われているアレを関数で引くことができる。
パッと思いつく感じだと、「今まではダイジェストの値を直接計算できなかったから QUERY_SAMPLE_TEXTカラムの値とかから何となく探していたけど、これからは直接 WHERE digest = STATEMENT_DIGEST('SELECT ..')とかで検索できる」というのがメリットとしてあるんだけれど、クエリーをノーマライズして一元化できるってことはつまりホワイトリストっぽいものが作れるんじゃないかなと思ったので軽くテスト。
まずはホワイトリストを登録するためのテーブルを作る。
大事なのは digestだけであって、 digest_textは単なるおまけ(後々見るのに楽かなって)
mysql80 7> SHOW CREATE TABLE myeval.whitelist\G
*************************** 1. row ***************************
Table: whitelist
Create Table: CREATE TABLE `whitelist` (
`digest` varchar(64) COLLATE utf8mb4_ja_0900_as_cs NOT NULL,
`digest_text` text COLLATE utf8mb4_ja_0900_as_cs
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_ja_0900_as_cs
1 row in set (0.01 sec)
( ´-`).oO(utf8mb4_binのがいいかな
これに例えばこんなクエリーでホワイトリスト登録する。
mysql80 7> INSERT INTO myeval.whitelist VALUES (STATEMENT_DIGEST('SELECT * FROM d1.t1 WHERE num = 1'), STATEMENT_DIGEST_TEXT('SELECT * FROM d1.t1 WHERE num = 1'));
Query OK, 1 row affected (0.00 sec)

mysql80 7> SELECT * FROM myeval.whitelist;
+------------------------------------------------------------------+--------------------------------------------+
| digest | digest_text |
+------------------------------------------------------------------+--------------------------------------------+
| d214d5d8f31ce686d36be01a22bc7cfff76dd8b7b131644c7fcad28e76f78489 | SELECT * FROM `d1` . `t1` WHERE `num` = ? |
+------------------------------------------------------------------+--------------------------------------------+
1 row in set (0.01 sec)
num = 1の部分はどうせノーマライズされるのでテキトーな値。
これに、「 myeval.whitelistに登録があればそのクエリーを実行、なければError: 1142をレイズする」ストアドプロシージャを用意する。
delimiter //
CREATE PROCEDURE myeval.eval_query (IN sql_statement TEXT)
BEGIN
DECLARE is_white TINYINT;
SELECT (digest IS NOT NULL) FROM myeval.whitelist WHERE digest = STATEMENT_DIGEST(sql_statement) INTO is_white;
IF is_white = 1 THEN
SET @sql_statement := sql_statement;
PREPARE st FROM @sql_statement;
EXECUTE st;
DEALLOCATE PREPARE st;
ELSE
SIGNAL SQLSTATE '42000' SET MESSAGE_TEXT = "Query isn't registored in myeval.whitelist", MYSQL_ERRNO = 1142;
END IF;
END
//
delimiter ;
このプロシージャーを実行する権限だけを持たせたユーザーを用意して
mysql80 7> CREATE USER yoku0825;
Query OK, 0 rows affected (0.00 sec)

mysql80 7> GRANT EXECUTE ON PROCEDURE myeval.eval_query TO yoku0825;
Query OK, 0 rows affected (0.03 sec)
そのアカウントでログイン。
mysql80 9> SHOW GRANTS;
+--------------------------------------------------------------------+
| Grants for yoku0825@% |
+--------------------------------------------------------------------+
| GRANT USAGE ON *.* TO `yoku0825`@`%` |
| GRANT EXECUTE ON PROCEDURE `myeval`.`eval_query` TO `yoku0825`@`%` |
+--------------------------------------------------------------------+
2 rows in set (0.00 sec)
当然このアカウントでは直接 d1.t1に対するアクセスはできないけれども、 SQL SECURITY DEFINERなストアドプロシージャを通せば、ストアドを作ったアカウントの権限でそのSQLが実行できるようになる。
mysql80 9> SELECT * FROM d1.t1 WHERE num = 1;
ERROR 1142 (42000): SELECT command denied to user 'yoku0825'@'localhost' for table 't1'

mysql80 9> CALL myeval.eval_query("SELECT * FROM d1.t1 WHERE num = 1");
+-----+------+
| num | val |
+-----+------+
| 1 | one |
+-----+------+
1 row in set (0.01 sec)

Query OK, 0 rows affected (0.01 sec)

mysql80 9> CALL myeval.eval_query("SELECT * FROM d1.t1 WHERE num = 2");
+-----+------+
| num | val |
+-----+------+
| 2 | two |
+-----+------+
1 row in set (0.00 sec)

Query OK, 0 rows affected (0.00 sec)
ダイジェストが一致すれば通すので、細かい定数部分が違っても問題なく。
mysql80 9> CALL myeval.eval_query("SELECT * FROM d1.t1 WHERE val = 'one'");
ERROR 1142 (42000): Query isn't registored in myeval.whitelist

mysql80 9> CALL myeval.eval_query("SELECT * FROM d1.t1 WHERE num < 2");

ERROR 1142 (42000): Query isn't registored in myeval.whitelist

mysql80 9> CALL myeval.eval_query("INSERT INTO d1.t1 VALUES (3, 'three')");
ERROR 1142 (42000): Query isn't registored in myeval.whitelist
ただしステートメントがそもそも違うものや、対象カラム、演算子が違うとダイジェストが変わるので弾かれる。
mysql80 7> INSERT INTO myeval.whitelist VALUES (STATEMENT_DIGEST('INSERT INTO d1.t1 VALUES (1, "one")'), STATEMENT_DIGEST_TEXT('INSERT INTO d1.t1 VALUES (1, "one")'));
Query OK, 1 row affected (0.01 sec)

mysql80 7> SELECT * FROM myeval.whitelist;
+------------------------------------------------------------------+--------------------------------------------+
| digest | digest_text |
+------------------------------------------------------------------+--------------------------------------------+
| d214d5d8f31ce686d36be01a22bc7cfff76dd8b7b131644c7fcad28e76f78489 | SELECT * FROM `d1` . `t1` WHERE `num` = ? |
| bcaf175197bfc4753d6de62d76dcd05484a9cb5ca65f4cb2f4b1b065c5e6ae0d | INSERT INTO `d1` . `t1` VALUES (...) |
+------------------------------------------------------------------+--------------------------------------------+
2 rows in set (0.00 sec)
テキトーに myeval.whitelistに登録してやれば
mysql80 9> CALL myeval.eval_query("INSERT INTO d1.t1 VALUES (3, 'three')");
Query OK, 0 rows affected (0.01 sec)

mysql80 9> CALL myeval.eval_query("SELECT * FROM d1.t1 WHERE num = 3");
+-----+-------+
| num | val |
+-----+-------+
| 3 | three |
+-----+-------+
1 row in set (0.00 sec)

Query OK, 0 rows affected (0.00 sec)
特に再読み込みとかせずに実行できるようにできる。
MySQL Enterprise FirewallみたいなことをSQLだけでできそうな予感がしたのでやってみました。
SQLだけでできるってことは、某RDSとかでも8.0が来たら出来るかも? とか :-P

MySQL 8.0のnutshellを読んで秘伝のタレをどうこうしようと思っているメモ

$
0
0
たぶん本当にメモ。
caching_sha2_password, it is now the preferred authentication plugin
innodb_undo_log_truncate is enabled by default.
The default innodb_autoinc_lock_mode setting is now 2 (interleaved).
The default character set has changed from latin1 to utf8mb4.
Added support in MySQL 8.0.2 for partial, in-place updates of JSON column values
The TempTable storage engine replaces the MEMORY storage engine as the default engine for in-memory internal temporary tables.
[mysqld]
default_authentication_plugin= mysql_native_password ### For client compatibility

innodb_undo_log_truncate= OFF

innodb_autoinc_lock_mode= 1 ### For binlog_format != ROW

character_set_server= utf8mb4 ### Default.
collation_server = utf8mb4_bin ### or utf8mb4_ja_0900_as_cs

##binlog_row_value_options= PARTIAL_JSON ### When stepping into mine-field

## internal_tmp_mem_storage_engine = MEMORY ### 5.7 style
## max_heap_table_size= 128M ### 128M is an example, 5.7 style
internal_tmp_mem_storage_engine = TempTable ### 8.0 style, default
temptable_max_ram= 128M ### is default 1G too large?


【2018/03/20 17:18】
ナッツシェルには書いてないけど、X Pluginを無効化するための設定。


mysqlx= OFF

なるべく負荷をかけずにInnoDBバッファプールに載っているページの情報を見る

$
0
0

TL;DR

  • information_schema.innodb_buffer_pageは重い
  • ib_buffer_poolにはテーブルスペースIDが記録されるので、それを使ってほげほげする
  • こんな感じ?
mysql> SET GLOBAL innodb_buffer_pool_dump_now = 1;
mysql> SELECT space, name FROM information_schema.innodb_sys_tablespaces INTO OUTFILE '/tmp/space.txt';

$ awk -F, '{print $1}' /var/lib/mysql/ib_buffer_pool | sort | join - <(sort /tmp/space.txt) | uniq -c | sort -n -r -k 1 | head
54570 50 hogehoge/fugafuga
12192 27 hogehoge/message
10494 31 hogehoge/piyopiyo
9683 42 hogehoge/magomago
6103 30 hogehoge/message_inbox

このバグレポートを見て「うん、知ってた」感があって( sys.innodb_buffer_statsu_by_tableinformation_schema.innodb_buffer_pageをベーステーブルにした ビューなので)なんかのたびに「このテーブル刺さるよ」みたいな話もしていた気がするけれど、そういえば最近編み出したワークアラウンドって書いてないなと思ったので書いておく。
で、やることは ib_buffer_poolファイル(InnoDBの暖気に使うアレ)からテーブルスペースIDを引っ張ってきて、 information_schema.innodb_sys_tablespaces(テーブルスペースIDとテーブル名の紐づけができる)と突き合わせるだけ。
mysql> SET GLOBAL innodb_buffer_pool_dump_now = 1;
mysql> SELECT space, name FROM information_schema.innodb_sys_tablespaces INTO OUTFILE '/tmp/space.txt';

$ awk -F, '{print $1}' /var/lib/mysql/ib_buffer_pool | sort | join - <(sort /tmp/space.txt) | uniq -c | sort -n -r -k 1 | head
54570 50 hogehoge/fugafuga
12192 27 hogehoge/message
10494 31 hogehoge/piyopiyo
9683 42 hogehoge/magomago
6103 30 hogehoge/message_inbox
innodb_file_per_table = 0だとたぶん上手くいかない(共有テーブルスペースに載ったテーブルはスペースID 0で出てくるので joinしようがない)
これならほとんど負荷なくInnoDBバッファプールの中身をチラ見することができる。ただし、ページNoとインデックス名を紐づけられるような情報は information_schemaにはないので、あくまでテーブル単位。

yum版のMySQL 8.0をCentOSなコンテナーにインストールすると Operation not permitted って言われる… (ビルド時にsetcap cap_sys_nice+ep されてた)

$
0
0

TL;DR

  • docker runする時に --cap-add=SYS_NICEを付け加えてやると上手くいく

$ docker run -it centos:centos7 bash
[root@457e75eaa657 /]# yum install -y https://dev.mysql.com/get/mysql80-community-release-el7-1.noarch.rpm
[root@457e75eaa657 /]# yum install -y mysql-community-server
[root@457e75eaa657 /]# mysqld --version
bash: /usr/sbin/mysqld: Operation not permitted
なんだこりゃ。
MySQL 5.7では起こらない。
$ docker run -it centos:centos7 bash
[root@05b4d1450b64 /]# yum install -y https://dev.mysql.com/get/mysql80-community-release-el7-1.noarch.rpm
[root@05b4d1450b64 /]# yum install -y --disablerepo="mysql80-community" --enablerepo="mysql57-community" mysql-community-server
[root@05b4d1450b64 /]# mysqld --version
mysqld Ver 5.7.22 for Linux on x86_64 (MySQL Community Server (GPL))
MySQL 8.0.11でも、”Linux Generic” のtarボールでは起こらない。
$ docker run -it centos:centos7 bash
[root@cb720d43d956 /]# yum install -y wget tar libaio numactl
[root@cb720d43d956 /]# wget https://dev.mysql.com/get/Downloads/MySQL-8.0/mysql-8.0.11-linux-glibc2.12-x86_64.tar.gz
[root@cb720d43d956 /]# tar xf mysql-8.0.11-linux-glibc2.12-x86_64.tar.gz
[root@cb720d43d956 /]# mv -i mysql-8.0.11-linux-glibc2.12-x86_64 /usr/local/mysql
[root@cb720d43d956 /]# /usr/local/mysql/bin/mysqld --version
/usr/local/mysql/bin/mysqld Ver 8.0.11 for linux-glibc2.12 on x86_64 (MySQL Community Server - GPL)
straceで叩いてみたりしたけど、最初の execveで直接EPERMが返ってきちゃって何をしようとした時に権限が足りないのか全く分からない…。
[root@cb720d43d956 /]# strace -f mysqld --version
execve("/usr/sbin/mysqld", ["mysqld", "--version"], [/* 8 vars */]) = -1 EPERM (Operation not permitted)
write(2, "strace: exec: Operation not perm"..., 38strace: exec: Operation not permitted
) = 38
exit_group(1) = ?
+++ exited with 1 +++
取り敢えず地味な切り分けの結果、 SYS_NICEを追加すれば動くことはわかった。
何だろう。 mbindもこのケーパビリティーで制御されるっぽいから ヌゥ馬かしら。
$ docker run -it --cap-add=SYS_NICE centos:centos7 bash
[root@2c042726caa1 /]# yum install -y https://dev.mysql.com/get/mysql80-community-release-el7-1.noarch.rpm
[root@2c042726caa1 /]# yum install -y mysql-community-server
[root@2c042726caa1 /]# mysqld --version
/usr/sbin/mysqld Ver 8.0.11 for Linux on x86_64 (MySQL Community Server - GPL)
MySQL公式のDockerリポジトリーで使ってる、 mysql-community-server-minimalのパッケージではこれは起きない。
$ docker run -it centos:centos7 bash
[root@6ff99c37bb08 /]# yum install -y https://repo.mysql.com/yum/mysql-8.0-community/docker/x86_64/mysql-community-server-minimal-8.0.11-1.el7.x86_64.rpm
[root@6ff99c37bb08 /]# mysqld --version
/usr/sbin/mysqld Ver 8.0.11 for Linux on x86_64 (MySQL Community Server - GPL)
いまいち原因ははっきりしないけど、コンテナーでほげほげしようとしている誰かに届けば幸い。


【2018/05/22 10:08】
@withgodさんに教えていただいた。












MySQL :: MySQL 8.0 Reference Manual :: 8.12.5 Resource Groups

微妙に書いてあった。

"「7の倍数」を表す正規表現"をMySQl 8.0で試す

$
0
0
1年半くらい前に書かれたらしいけれど、ふと今日 「7の倍数」を表す正規表現 - Qiitaを見つけて読んだ。
(取り敢えず今の俺の中で)正規表現といえばMySQL 8.0。
そして(取り敢えず今の俺の中で)forループ的に数値をテストするといえばCTE、CTEといえばMySQL 8.0。
やってみます。

さすがに元の正規表現は長くて直接クエリーに記述してるとめげるのでストアドファンクションにラップする。
mysql80 26> CREATE FUNCTION regexp_7(n BIGINT UNSIGNED) RETURNS INT DETERMINISTIC RETURN n RLIKE '\\A(((((([07]|(6[29]*3) <snip> )))))))))+\\z';
Query OK, 0 rows affected (0.03 sec)
バックスラッシュは二重にしなければならない、くらいで意外とすんなりストアドファンクションにできた。動くかどうかはわからない。
mysql80 26> WITH RECURSIVE seq AS(
-> SELECT 1 AS n
-> UNION ALL
-> SELECT n + 1 FROM seq WHERE n < 10
-> )
-> SELECT n FROM seq;
+------+
| n |
+------+
| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
| 6 |
| 7 |
| 8 |
| 9 |
| 10 |
+------+
10 rows in set (0.00 sec)
そしてまあ簡単な再起CTE。
組み合わせるとこうなる。
mysql80 26> WITH RECURSIVE seq AS( SELECT 1 AS n UNION ALL SELECT n + 1 FROM seq WHERE n < 10 ) SELECT n, regexp_7(n) AS r FROM seq;
+------+------+
| n | r |
+------+------+
| 1 | 0 |
| 2 | 0 |
| 3 | 0 |
| 4 | 0 |
| 5 | 0 |
| 6 | 0 |
| 7 | 1 |
| 8 | 0 |
| 9 | 0 |
| 10 | 0 |
+------+------+
10 rows in set (0.12 sec)
nが正規表現にマッチした時はrが1、マッチしなければ0。
取り敢えず1~10の範囲では問題なく(MySQLの正規表現エンジンが)動いている様子。
mysql80 26>  WITH RECURSIVE seq AS(
-> SELECT 1 AS n, 0 AS r
-> UNION ALL
-> SELECT n + 1, regexp_7(n + 1) FROM seq WHERE n < 49
-> )
-> SELECT n, r FROM seq WHERE r = 1;
+------+------+
| n | r |
+------+------+
| 7 | 1 |
| 14 | 1 |
| 21 | 1 |
| 28 | 1 |
| 35 | 1 |
| 42 | 1 |
| 49 | 1 |
+------+------+
7 rows in set (0.56 sec)
いい感じに見やすくなったのでnを1~49まで増やしてる。重い。
ついでにnと前回のn(LAG(n))の差を取れば常に7になるはずだと思ってWindow関数にも手を出してみる。
mysql80 26>  WITH RECURSIVE seq AS(
-> SELECT 1 AS n, 0 AS r
-> UNION ALL
-> SELECT n + 1, regexp_7(n + 1) FROM seq WHERE n < 49
-> )
-> SELECT n, r, n - LAG(n) OVER (ORDER BY n) AS diff FROM seq WHERE r = 1;
+------+------+------+
| n | r | diff |
+------+------+------+
| 7 | 1 | NULL |
| 14 | 1 | 7 |
| 21 | 1 | 7 |
| 28 | 1 | 7 |
| 35 | 1 | 7 |
| 42 | 1 | 7 |
| 49 | 1 | 7 |
+------+------+------+
7 rows in set (0.62 sec)
じゃあここまでをもう一段CTEに閉じ込めて、diff <> 7のものを探してみる。
mysql80 26> WITH RECURSIVE seq AS( SELECT 1 AS n, 0 AS r UNION ALL SELECT n + 1, regexp_7(n + 1) FROM seq WHERE n < 1000 ),
-> ret AS( SELECT n, r, n - LAG(n) OVER (ORDER BY n) AS diff FROM seq WHERE r = 1)
-> SELECT * FROM ret WHERE diff <> 7;
Empty set (12.60 sec)
1~1000で12秒なら、1万件で2分くらいかしらん、と思ったら↓に当たった。
mysql80 26> SET cte_max_recursion_depth = 100000;
Query OK, 0 rows affected (0.00 sec)

mysql80 26> WITH RECURSIVE seq AS( SELECT 1 AS n, 0 AS r UNION ALL SELECT n + 1, regexp_7(n + 1) FROM seq WHERE n < 10000 ),
-> ret AS( SELECT n, r, n - LAG(n) OVER (ORDER BY n) AS diff FROM seq WHERE r = 1)
-> SELECT * FROM ret WHERE diff <> 7;
Empty set (2 min 19.87 sec)
うん、楽しい、と、思う。

default_collation_for_utf8mb4 なんてパラメーターが追加になっていた

$
0
0
何このパラメーター、と思ったら、 utf8mb4のデフォルトコレーションが utf8mb4_general_ci (MySQL 5.7とそれ以前) から utf8mb4_0900_ai_ci (MySQL 8.0)に変わったことに対する経過措置っぽかった。
これを utf8mb4_general_ciにセットしておくと、コレーションを指定せずに utf8mb4を使った時に今まで通り utf8mb4_general_ciを使ってくれるということ。
当然 CREATE TABLE, ALTER TABLEでは想像したように動いて
mysql80 13> SELECT @@default_collation_for_utf8mb4;
+---------------------------------+
| @@default_collation_for_utf8mb4 |
+---------------------------------+
| utf8mb4_general_ci |
+---------------------------------+
1 row in set (0.00 sec)

mysql80 13> CREATE TABLE t1 (num serial, val varchar(32) CHARSET utf8mb4);
Query OK, 0 rows affected (0.03 sec)

mysql80 13> show create table t1\G
*************************** 1. row ***************************
Table: t1
Create Table: CREATE TABLE `t1` (
`num` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`val` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
UNIQUE KEY `num` (`num`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_ja_0900_as_cs
1 row in set (0.00 sec)
さらにコレーションを指定しなかった時のキャラクターセットのキャスト(知ってましたかこんな機能。俺は知ってたけど使ったことはない)でもそのように動く。
mysql80 13> SELECT @@default_collation_for_utf8mb4;
+---------------------------------+
| @@default_collation_for_utf8mb4 |
+---------------------------------+
| utf8mb4_general_ci |
+---------------------------------+
1 row in set (0.00 sec)

mysql80 13> SELECT _utf8mb4 'A' = _utf8mb4 'A';
+-------------------------------+
| _utf8mb4 'A' = _utf8mb4 'A' |
+-------------------------------+
| 0 |
+-------------------------------+
1 row in set (0.00 sec)

mysql80 13> SELECT @@default_collation_for_utf8mb4;
+---------------------------------+
| @@default_collation_for_utf8mb4 |
+---------------------------------+
| utf8mb4_0900_ai_ci |
+---------------------------------+
1 row in set (0.00 sec)

mysql80 13> SELECT _utf8mb4 'A' = _utf8mb4 'A';
+-------------------------------+
| _utf8mb4 'A' = _utf8mb4 'A' |
+-------------------------------+
| 1 |
+-------------------------------+
1 row in set (0.00 sec)
日本人には kamipoのハハ=パパ問題を引き起こすことで有名だけれど、ヨーロッパ圏もやっぱりアクセントが区別されなくなるのは大事だったんだなぁ、とか思ったり思わなかったり。
なお、経過措置なのでこのパラメーターには初め(導入はMySQL 8.0.11)からDEPRECATEDなワーニングを生成するようになっている。
mysql80 13> SET SESSION default_collation_for_utf8mb4 = 'utf8mb4_0900_ai_ci';
Query OK, 0 rows affected, 1 warning (0.00 sec)

mysql80 13> SHOW WARNINGS;
+---------+------+--------------------------------------------------------------------------------------------------------+
| Level | Code | Message |
+---------+------+--------------------------------------------------------------------------------------------------------+
| Warning | 1681 | Updating 'default_collation_for_utf8mb4' is deprecated. It will be made read-only in a future release. |
+---------+------+--------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)


【2018/05/29 14:48】
ちなみに、取りうる値は utf8mb4_general_ciutf8mb4_0900_ai_ciなので、 utf8mb4_ja_0900_as_csとか指定しようとしてもダメなのです!


mysql80 13> SET SESSION default_collation_for_utf8mb4 = 'utf8mb4_ja_0900_as_cs';
ERROR 3721 (HY000): Invalid default collation utf8mb4_ja_0900_as_cs: utf8mb4_0900_ai_ci or utf8mb4_general_ci expected






MySQL 8.0にはperformance_schema.events_statements_summary_by_digest にQUERY_SAMPLE_TEXTカラムが追加された

$
0
0
MySQL 5.7からMySQL 8.0でのevents_statements_summary_by_digestのカラム変更。
$ diff -y --suppress-common-lines <(mysql57 -sse "DESC p_s.events_statements_summary_by_digest")
<(mysql80 -sse "DESC p_s.events_statements_summary_by_digest")
DIGEST varchar(32) YES NULL | DIGEST varchar(64) YES NULL
FIRST_SEEN timestamp NO 0000-00-00 00 | FIRST_SEEN timestamp(6) NO 0000-00-00 00
LAST_SEEN timestamp NO 0000-00-00 00 | LAST_SEEN timestamp(6) NO 0000-00-00 00
> QUANTILE_95 bigint(20) unsigned NO NULL
> QUANTILE_99 bigint(20) unsigned NO NULL
> QUANTILE_999 bigint(20) unsigned NO NULL
> QUERY_SAMPLE_TEXT longtext YES NULL
> QUERY_SAMPLE_SEEN timestamp(6) NO 0000-
> QUERY_SAMPLE_TIMER_WAIT bigint(20) unsigned NO
  • FISRST_SEEN, LAST_SEENのマイクロ秒対応
  • QUANTILE_*, QUERY_SAMPLE_*カラムの追加
そのうち、 QUERY_SAMPLE_*の動作に関わる performance_schema_max_digest_sample_ageのはなし。

を読む感じ、
  • performance_schema_max_digest_sample_age = 0の時
    • “Resampling based on wait times” で、 TIMER_WAITが現在の QUERY_SAMPLE_TIMER_WAITを超えたらサンプルを更新する
  • performance_schema_max_digest_sample_age > 0の時
    • 最後に QUERY_SAMPLE_*を保管した時刻から performance_schema_max_digest_sample_age秒以上経ってまた同じダイジェストが記録された時にサンプルを更新する
という動きなようす。
サンプルは STATEMENT_DIGESTみたいなものでノーマライズされる前の生のSQL。
というわけでPKを変えながら10万回SELECTを流しながら、このテーブルを覗いてみた。
$ for n in $(seq 1 100000) ; do
> mysql80 -e "SELECT * FROM d1.t1 WHERE num = $n"> /dev/null
> done

mysql80 100022> SELECT * FROM p_s.events_statements_summary_by_digest WHERE count_star > 10\G...
*************************** 3. row ***************************
SCHEMA_NAME: NULL
DIGEST: d214d5d8f31ce686d36be01a22bc7cfff76dd8b7b131644c7fcad28e76f78489
DIGEST_TEXT: SELECT * FROM `d1` . `t1` WHERE `num` = ?
COUNT_STAR: 100000
SUM_TIMER_WAIT: 18943715535000
MIN_TIMER_WAIT: 121014000
AVG_TIMER_WAIT: 189437000
MAX_TIMER_WAIT: 5925412000
SUM_LOCK_TIME: 8737613000000
SUM_ERRORS: 0
SUM_WARNINGS: 0
SUM_ROWS_AFFECTED: 0
SUM_ROWS_SENT: 100000
SUM_ROWS_EXAMINED: 100000
SUM_CREATED_TMP_DISK_TABLES: 0
SUM_CREATED_TMP_TABLES: 0
SUM_SELECT_FULL_JOIN: 0
SUM_SELECT_FULL_RANGE_JOIN: 0
SUM_SELECT_RANGE: 0
SUM_SELECT_RANGE_CHECK: 0
SUM_SELECT_SCAN: 0
SUM_SORT_MERGE_PASSES: 0
SUM_SORT_RANGE: 0
SUM_SORT_ROWS: 0
SUM_SORT_SCAN: 0
SUM_NO_INDEX_USED: 0
SUM_NO_GOOD_INDEX_USED: 0
FIRST_SEEN: 2018-05-30 14:44:59.376251
LAST_SEEN: 2018-05-30 15:00:46.526876
QUANTILE_95: 251188643
QUANTILE_99: 363078054
QUANTILE_999: 660693448
QUERY_SAMPLE_TEXT: SELECT * FROM d1.t1 WHERE num = 96806
QUERY_SAMPLE_SEEN: 2018-05-30 15:00:17.487788
QUERY_SAMPLE_TIMER_WAIT: 1973581000
3 rows in set (0.01 sec)
ちょくちょく QUERY_SAMPLE_*が変わったり変わらなかったりする。
個人的には performance_schema_max_digest_sample_age = 0にして「一番時間がかかった時のサンプル」を残しておくのがいいかなと思ったり思わなかったり。
ちなみに、ビューに対するクエリーの場合、サンプルはビューに対するアクセスのクエリーで DIGEST_TEXTはビューを展開した後のクエリーをノーマライズするので一瞬「んっ?」となった。
慣れれば平気。
mysql80 100022> SELECT * FROM p_s.events_statements_summary_by_digest WHERE count_star > 10\G
*************************** 1. row ***************************
SCHEMA_NAME: p_s
DIGEST: 9cbd44d9fdf48860a2f21597fd1d543130319eb800204c6ed51491e68ff55dcd
DIGEST_TEXT: SELECT `performance_schema` . `events_statements_summary_by_digest` . `SCHEMA_NAME` AS `SCHEMA_NAME` , `performance_schema` . `events_statements_summary_by_digest` . `DIGEST` AS `DIGEST` , `performance_schema` . `events_statements_summary_by_digest` . `DIGEST_TEXT` AS `DIGEST_TEXT` , `performance_schema` . `events_statements_summary_by_digest` . `COUNT_STAR` AS `COUNT_STAR` , `performance_schema` . `events_statements_summary_by_digest` . `SUM_TIMER_WAIT` AS `SUM_TIMER_WAIT` , `performance_schema` . `events_statements_summary_by_digest` . `MIN_TIMER_WAIT` AS `MIN_TIMER_WAIT` , `performance_schema` . `events_statements_summary_by_digest` . `AVG_TIMER_WAIT` AS `AVG_TIMER_WAIT` , `performance_schema` . `events_statements_summary_by_digest` . `MAX_TIMER_WAIT` AS `MAX_TIMER_WAIT` , `performance_schema` . `events_statements_summary_by_digest` . `SUM_LOCK_TIME` AS `SUM_LOCK_TIME` , `performance_schema` . `events_statements_summary_by_digest` . `SUM_ERRORS` AS `SUM_ERRORS` ,
COUNT_STAR: 12
SUM_TIMER_WAIT: 24253861000
MIN_TIMER_WAIT: 925197000
AVG_TIMER_WAIT: 2021155000
MAX_TIMER_WAIT: 6172994000
SUM_LOCK_TIME: 6760000000
SUM_ERRORS: 0
SUM_WARNINGS: 0
SUM_ROWS_AFFECTED: 0
SUM_ROWS_SENT: 25
SUM_ROWS_EXAMINED: 64
SUM_CREATED_TMP_DISK_TABLES: 0
SUM_CREATED_TMP_TABLES: 0
SUM_SELECT_FULL_JOIN: 0
SUM_SELECT_FULL_RANGE_JOIN: 0
SUM_SELECT_RANGE: 0
SUM_SELECT_RANGE_CHECK: 0
SUM_SELECT_SCAN: 12
SUM_SORT_MERGE_PASSES: 0
SUM_SORT_RANGE: 0
SUM_SORT_ROWS: 0
SUM_SORT_SCAN: 0
SUM_NO_INDEX_USED: 12
SUM_NO_GOOD_INDEX_USED: 0
FIRST_SEEN: 2018-05-30 17:20:00.342417
LAST_SEEN: 2018-05-30 17:21:16.207898
QUANTILE_95: 6309573444
QUANTILE_99: 6309573444
QUANTILE_999: 6309573444
QUERY_SAMPLE_TEXT: SELECT * FROM p_s.events_statements_summary_by_digest WHERE count_star > 10
QUERY_SAMPLE_SEEN: 2018-05-30 17:21:11.760156
QUERY_SAMPLE_TIMER_WAIT: 1947630000

MySQL ShellのUpgrade CheckerをPerl 5に書き下してみたけどそんなことする必要はなかったようだ

$
0
0

TL;DR


MySQLの中の人が最近(?)ちょくちょく推してる Upgrade Checkerだけど、MySQL Shellの機能なのでX Plugin必須かなと思ってちょっと敬遠していたのだけれど。
checkForServerUpgrade() can use either an X Protocol connection or a classic MySQL protocol connection.
ってなことで実はPerlに打ち直す必要なんてなかったことがこの記事を書いている最中に判明した。かなしい。
$ mysqlsh -S /usr/mysql/5.7.22/data/mysql.sock -uroot --mysql
Creating a Classic session to 'root@/usr%2Fmysql%2F5.7.22%2Fdata%2Fmysql.sock'
Enter password:
Fetching schema names for autocompletion... Press ^C to stop.
Your MySQL connection id is 77
Server version: 5.7.22-log Source distribution
No default schema selected; type \use <schema> to set one.
MySQL Shell 8.0.11

MySQL localhost JS > util.checkForServerUpgrade();
The MySQL server at /usr%2Fmysql%2F5.7.22%2Fdata%2Fmysql.sock will now be checked for compatibility issues for upgrade to MySQL 8.0...
MySQL version: 5.7.22-log - Source distribution
...
よいこはちゃんとドキュメントを読もう、という話で終わっちゃいそうだけれど、折角書き下したのでちょっと紹介。
シンタックスが気にくわなくて直したのはいくつかあるけれど、割と素直にSQLを移植しただけなのでSQLごとにちょっと感じが違うのはある。
あと、本家はワーニングとエラーと分けられているけれど面倒だったので全部 Test::More でOKかNGかしか返さなくしちゃった。
チェック全体の流れとしてはざっとこんな感じ。
  ok(get_reserved_keywords_check(),
"Usage of db objects with names conflicting with reserved keywords in 8.0");
ok(get_utf8mb3_check(), "Usage of utf8mb3 charset");
ok(get_zerofill_check(), "Usage of use ZEROFILL/display length type attributes");
ok(Check_table_command(), "Issues reported by 'check table x for upgrade' command");
ok(get_mysql_schema_check(), "Table names in the mysql schema conflicting with new tables in 8.0");
ok(get_old_temporal_check(), "Usage of old temporal type");
ok(get_foreign_key_length_check(), "Foreign key constraint names longer than 64 characters");
ok(get_maxdb_sql_mode_flags_check(), "usage of obsolete MAXDB sql_mode flag");
ok(get_obsolete_sql_mode_flags_check(), "get_obsolete_sql_mode_flags_check");
ok(get_partitioned_tables_in_shared_tablespaces_check(), "Usage of partitioned tables in shared tablespaces");
ok(get_removed_functions_check(), "get_removed_functions_check");
予約語が使われているオブジェクトがないか、utf8mb3(3バイトUTF-8)が使われているオブジェクトはないか、zerofill属性を期待している(ように思われる)データ型はないか、 CHECK TABLE .. FOR UPGRADEは通るか、8.0で新しく追加されたテーブルと競合するオブジェクトはないか、5.5とそれ以前までで使用されていた古い形式のDATETIME/TIMESTAMP型(マイクロ秒非対応の古いデータ型)はないか、64文字を超える外部キー制約はないか、古い複合sql_modeが使われているオブジェクトはないか、ibdata1に格納されているパーティションはないか、削除された関数を使っているストアドやgenerated columnはないか、をチェックしている。
具体的なSQLは、 p5-mysql-upgrade-checker--executeなしで実行するか、general_log = ONにしてMySQL Shellから実行などすると良いと思われる。
あとはREADMEにサンプル( CHECK TABLE .. FOR UPGRADEだけ可変だけど他は固定)が貼ってあるのでそれでも雰囲気は感じ取ってもらえるのではないか。
なおこの get_reserved_keywords_checkは「新しく予約語になったもの」が使われていないかを探すけれど、「キーワードから予約語になって俺のスキーマを殺した row」とかは入っていないので注意。
プルリクチャンス?
Viewing all 581 articles
Browse latest View live