GA

2015/03/10

MySQL 5.7では暗黙のテンポラリーテーブルにもInnoDBが使われる

取り敢えずダミーデータを突っ込んだテーブルを自己結合しつつぐりぐりソートしてテンポラリーテーブルを作らせる。


$ perl -M"Digest::MD5 'md5_hex'" -e 'for ($n = 1; $n <= 1000000; $n++) { printf("%d\t%s\n", $n, md5_hex($n)); }' > /tmp/md5

mysql> create table t1 (num serial, val varchar(32));
Query OK, 0 rows affected (0.01 sec)

mysql> LOAD DATA INFILE '/tmp/md5' INTO TABLE t1;
Query OK, 1000000 rows affected (8.88 sec)
Records: 1000000  Deleted: 0  Skipped: 0  Warnings: 0

mysql> explain SELECT * FROM t1 LEFT JOIN t1 AS t2 USING(num) LEFT JOIN t1 AS t3 USING(num) ORDER BY t1.val ASC, t2.val DESC, t3.val ASC;
+----+-------------+-------+------------+--------+---------------+------+---------+-----------+--------+----------+---------------------------------+
| id | select_type | table | partitions | type   | possible_keys | key  | key_len | ref       | rows   | filtered | Extra                      |
+----+-------------+-------+------------+--------+---------------+------+---------+-----------+--------+----------+---------------------------------+
|  1 | SIMPLE      | t1    | NULL       | ALL    | NULL          | NULL | NULL    | NULL      | 996250 |   100.00 | Using temporary; Using filesort |
|  1 | SIMPLE      | t2    | NULL       | eq_ref | num           | num  | 8       | d1.t1.num |      1 |   100.00 | NULL                            |
|  1 | SIMPLE      | t3    | NULL       | eq_ref | num           | num  | 8       | d1.t1.num |      1 |   100.00 | NULL                      |
+----+-------------+-------+------------+--------+---------------+------+---------+-----------+--------+----------+---------------------------------+
3 rows in set, 1 warning (0.00 sec)

mysql> SHOW WARNINGS;
+-------+------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message                                                                                                                                                                                                                                                                                                                                          |
+-------+------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Note  | 1003 | /* select#1 */ select `d1`.`t1`.`num` AS `num`,`d1`.`t1`.`val` AS `val`,`d1`.`t2`.`val` AS `val`,`d1`.`t3`.`val` AS `val` from `d1`.`t1` left join `d1`.`t1` `t2` on((`d1`.`t1`.`num` = `d1`.`t2`.`num`)) left join `d1`.`t1` `t3` on((`d1`.`t1`.`num` = `d1`.`t3`.`num`)) where 1 order by `d1`.`t1`.`val`,`d1`.`t2`.`val` desc,`d1`.`t3`.`val` |
+-------+------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)



5.7.6。

mysql> SELECT * FROM t1 LEFT JOIN t1 AS t2 USING(num) LEFT JOIN t1 AS t3 USING(num) ORDER BY t1.val ASC, t2.val DESC, t3.val ASC;
1000000 rows in set (38.72 sec)

# pt-ioprofile
Tue Mar 10 17:21:50 JST 2015
Tracing process ID 15352
     total      pread     pwrite      lseek filename
  0.092978   0.000000   0.092895   0.000083 /usr/local/mysql/data/ibtmp1
  0.000578   0.000578   0.000000   0.000000 /usr/local/mysql/data/d1/t1.ibd

ibtmp1はテンポラリーテーブル専用のテーブルスペースファイルで、REDOログを書かない(テンポラリーテーブルはクラッシュリカバリーされないため)


5.6.23。

mysql [localhost] {msandbox} (d1) > SELECT * FROM t1 LEFT JOIN t1 AS t2 USING(num) LEFT JOIN t1 AS t3 USING(num) ORDER BY t1.val ASC, t2.val DESC, t3.val ASC;
1000000 rows in set (24.80 sec)

# pt-ioprofile
Tue Mar 10 17:26:44 JST 2015
Tracing process ID 15829
     total      pread       read      write       open      close      lseek filename
  9.197881   8.124282   0.000000   1.009717   0.000035   0.063840   0.000007 /home/mysql/sandboxes/msb_5_6_23/tmp/MY7xramM
  6.187275   5.180143   0.000000   0.933478   0.000064   0.073582   0.000008 /home/mysql/sandboxes/msb_5_6_23/tmp/MYK6H2em
  1.000256   0.000000   0.129345   0.863924   0.006977   0.000000   0.000010 /home/mysql/sandboxes/msb_5_6_23/tmp/MYWMZd7b
  0.304381   0.000000   0.216208   0.088095   0.000035   0.000012   0.000031 /home/mysql/sandboxes/msb_5_6_23/tmp/#sql_3dd5_0.MYD
  0.019654   0.000000   0.000014   0.000084   0.019523   0.000020   0.000013 /home/mysql/sandboxes/msb_5_6_23/tmp/#sql_3dd5_0.MYI
  0.000195   0.000000   0.000030   0.000080   0.000039   0.000040   0.000006 /home/mysql/sandboxes/msb_5_6_23/tmp/MY2rane3

いつもどおり、MyISAMなテンポラリーテーブルを作ってる。


【2015/04/30 15:24】
ibtmp1はmysqldが再起動されるまでサイズが小さくなりはしないので、暗黙のテンポラリーテーブルがあふれると死ぬ

MySQL 5.7.6のInnoDB日本語全文検索 MeCab Plugin

MySQL :: MySQL 5.7 Reference Manual :: 12.9.9 InnoDB MeCab Full-Text Parser Plugin の内容のおさらい。

まず、基本的なライブラリーと辞書は(この記事を書いている時点では).tar.gzバイナリーに同梱されているっぽいのでそちらを使う。Oracle公式のyumリポジトリー からインストールできるrpmには含まれていないように見えるので、その場合は別途突っ込まないといけないはずだけど、libpluginmecab.soが何かにダイナミックリンクしているわけではないので、辞書だけ取ってきてmecabrcに設定すればいけるような気がする。詳しく調べてない。


この環境はバイナリーの.tar.gzを取ってきて、/usr/local/mysqlに展開したとして、


$ ll /usr/local/mysql/lib/plugin/*mecab*
-rwxr-xr-x 1 root root 3988451 Feb 10 20:28 /usr/local/mysql/lib/plugin/libpluginmecab.so

$ ll -R /usr/local/mysql/lib/mecab
/usr/local/mysql/lib/mecab/:
total 8
drwxr-xr-x 5 root root 4096 Feb 17 10:54 dic
drwxr-xr-x 2 root root 4096 Feb 23 21:59 etc
..

plugin_dirにあたるlib/pluginにlibpluginmecab.soが、その他InnoDB MeCab Pluginに必要な辞書(dic)とか設定ファイル(etc)をおさめたディレクトリがlib/mecabにある。

続いてmy.cnfをゴニョる。


$ vim /etc/my.cnf
..
[mysqld]
loose-mecab-rc-file= /usr/local/mysql/lib/mecab/etc/mecabrc
innodb_ft_min_token_size= 1
..

mecab-rc-fileはlib/mecab/etc/mecabrcのパスを絶対パスで[mysqld]セクションに記述する。loose-接頭辞をつけておかないとMySQLが起動しなくなるので注意(INSTALL PLUGIN前にこのオプションを渡そうとすると、"unknown option"って言われてmysqldが起動してくれない)

参考: MySQL の unknown option エラーはオプションに loose- プレフィックスをつけると回避できる - かみぽわーる

innodb_ft_min_token_sizeはこのサイズより小さい文字列はトークンにしないというオプションだが、暗黙のデフォルトは3。英語で"a"とか"to"とかそういう頻出単語をトークナイズしないようにするためと書いてある。CJKでは1にセットしてね、とも。

my.cnfの次はmecabrc(↑のmy.cnfに記述したパスにあるもの)をゴニョる。



$ vim /usr/local/mysql/lib/mecab/etc/mecabrc
..
dicdir =  /usr/local/mysql/lib/mecab/dic/ipadic_utf-8

dicdirに、使いたい辞書の入っているディレクトリを指定する。


# ll lib/mecab/dic/
total 12
drwxr-xr-x 2 root root 4096 Feb 17 10:54 ipadic_euc-jp
drwxr-xr-x 2 root root 4096 Feb 17 10:54 ipadic_sjis
drwxr-xr-x 2 root root 4096 Feb 17 10:54 ipadic_utf-8

5.7.6現在、euc-jp, sjis, utf-8の3つが入ってる。5.7.7のリリースノート を見ると eucjpms, cp932, utf8mb4に対応したよ! と書いてあって、マニュアルのページには同じ辞書を使うよ、と書いてある。

この状態で起動してやると、


$ bin/mysqld_safe &
$ less data/error.log
..
2015-03-04T02:30:12.628925Z 0 [Warning] unknown variable 'loose-mecab-rc-file=/usr/local/mysql/lib/mecab/etc/mecabrc'
..

まだINSTALL PLUGINしてないので、unknown variableとして扱われる。


mysql> INSTALL PLUGIN mecab SONAME 'libpluginmecab.so';
Query OK, 0 rows affected (0.20 sec)

$ tail data/error.log
2015-03-04T03:21:46.236551Z 2 [Note] Mecab: Trying createModel(--rcfile=/usr/local/mysql/lib/mecab/etc/mecabrc)
2015-03-04T03:21:46.436600Z 2 [Note] Mecab: Loaded dictionary charset is utf-8

認識したぽい。


mysql> SHOW CREATE TABLE articles;
+----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table    | Create Table                                                                                                                                                                                                                                                                      |
+----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| articles | CREATE TABLE `articles` (
  `seq` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `title` text,
  `content` longtext,
  `timestamp` datetime DEFAULT NULL,
  UNIQUE KEY `seq` (`seq`),
  KEY `timestamp` (`timestamp`)
) ENGINE=InnoDB AUTO_INCREMENT=1914065 DEFAULT CHARSET=utf8 |
+----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

こんな感じのWikipediaのデータを食わせたテーブルに


mysql> ALTER TABLE articles ADD FULLTEXT KEY (title, content) WITH PARSER MeCab;
Query OK, 0 rows affected (1 hour 2 min 12.62 sec)
Records: 0  Duplicates: 0  Warnings: 0

$ tail data/error.log
2015-03-04T03:39:02.484153Z 0 [ERROR] Mecab:
2015-03-04T03:40:06.299126Z 0 [ERROR] Mecab:
2015-03-04T03:45:13.146195Z 0 [ERROR] Mecab:
2015-03-04T03:56:10.793337Z 0 [ERROR] Mecab:
2015-03-04T03:56:21.149414Z 0 [ERROR] Mecab:
2015-03-04T03:59:34.385256Z 0 [ERROR] Mecab:
2015-03-04T03:59:34.421553Z 0 [ERROR] Mecab:
2015-03-04T03:59:34.470875Z 0 [ERROR] Mecab:
2015-03-04T03:59:34.600808Z 0 [ERROR] Mecab:
2015-03-04T03:59:34.632828Z 0 [ERROR] Mecab:
2015-03-04T03:59:34.941143Z 0 [ERROR] Mecab:
2015-03-04T03:59:34.969369Z 0 [ERROR] Mecab:
2015-03-04T03:59:34.986076Z 0 [ERROR] Mecab:
2015-03-04T03:59:35.056543Z 0 [ERROR] Mecab:
2015-03-04T03:59:35.352131Z 0 [ERROR] Mecab:
2015-03-04T03:59:36.754206Z 0 [ERROR] Mecab:

なんかダイイングメッセージみたいに不明なエラー吐いてるけど(Mroongaと比較した感じでは"too long sentence"エラーのはず)取り敢えず無視して、

【2015/03/18 13:33】
バグレポートしましたが、5.7.8でFixedとのこと。
MySQL Bugs: #76164: InnoDB FTS with MeCab parser prints empty error message


mysql> SELECT COUNT(*) FROM articles WHERE match(title, content) against('データベース');
+----------+
| COUNT(*) |
+----------+
|     3013 |
+----------+
1 row in set (0.01 sec)

mysql> explain SELECT COUNT(*) FROM articles WHERE match(title, content) against ('データベース');
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra                        |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------------------+
|  1 | SIMPLE      | NULL  | NULL       | NULL | NULL          | NULL | NULL    | NULL | NULL |     NULL | Select tables optimized away |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------------------+
1 row in set, 1 warning (0.01 sec)

mysql> SHOW WARNINGS;
+-------+------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message                                                                                                                                                                               |
+-------+------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Note  | 1003 | /* select#1 */ select count(0) AS `COUNT(*)` from `wikipedia`.`articles` where (match `wikipedia`.`articles`.`title`,`wikipedia`.`articles`.`content` against ('データベース'))       |
+-------+------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.02 sec)

引けてるっぽい。


mysql> explain SELECT * FROM articles WHERE match(title, content) against ('データベース') LIMIT 10;
+----+-------------+----------+------------+----------+---------------+-------+---------+-------+------+----------+-------------------------------------------+
| id | select_type | table    | partitions | type     | possible_keys | key   | key_len | ref   | rows | filtered | Extra                                     |
+----+-------------+----------+------------+----------+---------------+-------+---------+-------+------+----------+-------------------------------------------+
|  1 | SIMPLE      | articles | NULL       | fulltext | title         | title | 0       | const |    1 |   100.00 | Using where; Ft_hints: sorted, limit = 10 |
+----+-------------+----------+------------+----------+---------------+-------+---------+-------+------+----------+-------------------------------------------+
1 row in set, 1 warning (0.02 sec)

mysql> SELECT * FROM articles WHERE match(title, content) against ('データベース') LIMIT 10;
..
10 rows in set (0.01 sec)

バッファプールに載ってて単一条件ならまあまあ動くんだけど


mysql> SELECT * FROM articles WHERE match(title, content) against ('データベース') ORDER BY timestamp DESC LIMIT 10;
..
10 rows in set (0.62 sec)

スコア以外のところでソートするとやっぱり死ねるねぇ。。


【2015/03/10 18:57】
Ngramの方も書きました => 日々の覚書: MySQL 5.7.6のInnoDB日本語全文検索 ngram

MySQL 5.7.6のPerformance SchemaでInnoDBのALTER TABLE進捗どうですか

MySQL 5.7.6で追加された新しいp_sのステージ情報から、 合法的に InnoDBに進捗どうですか? を聞けるようになったらしい。

MySQL :: MySQL 5.7 Reference Manual :: 14.13.11.1 Monitoring ALTER TABLE Progress for InnoDB Tables Using Performance Schema

setup_instrumentsでalter table関連のやつ(デフォルトOFF)と


mysql> SELECT * FROM setup_instruments WHERE name LIKE 'stage/innodb/alter%';
+------------------------------------------------------+---------+-------+
| NAME                                                 | ENABLED | TIMED |
+------------------------------------------------------+---------+-------+
| stage/innodb/alter table (end)                       | NO      | NO    |
| stage/innodb/alter table (flush)                     | NO      | NO    |
| stage/innodb/alter table (insert)                    | NO      | NO    |
| stage/innodb/alter table (log apply index)           | NO      | NO    |
| stage/innodb/alter table (log apply table)           | NO      | NO    |
| stage/innodb/alter table (merge sort)                | NO      | NO    |
| stage/innodb/alter table (read PK and internal sort) | NO      | NO    |
+------------------------------------------------------+---------+-------+
7 rows in set (0.11 sec)

setup_consumersのevents_stages_*もデフォルトはOFF


mysql> SELECT * FROM setup_consumers WHERE name LIKE '%stages%';
+----------------------------+---------+
| NAME                       | ENABLED |
+----------------------------+---------+
| events_stages_current      | NO      |
| events_stages_history      | NO      |
| events_stages_history_long | NO      |
+----------------------------+---------+
3 rows in set (0.08 sec)

これを

mysql> UPDATE setup_instruments SET enabled= 'YES' WHERE name LIKE 'stage/innodb/alter%';
Query OK, 7 rows affected (0.03 sec)
Rows matched: 7  Changed: 7  Warnings: 0

mysql> UPDATE setup_consumers SET enabled= 'YES' WHERE name LIKE '%stages%';
Query OK, 3 rows affected (0.03 sec)
Rows matched: 3  Changed: 3  Warnings: 0

こうじゃ!


mysql> ALTER TABLE order_line ADD KEY (ol_dist_info);
..

mysql> SELECT EVENT_NAME, WORK_COMPLETED, WORK_ESTIMATED FROM events_stages_current;
+------------------------------------------------------+----------------+----------------+
| EVENT_NAME                                           | WORK_COMPLETED | WORK_ESTIMATED |
+------------------------------------------------------+----------------+----------------+
| stage/innodb/alter table (read PK and internal sort) |          12084 |         268542 |
+------------------------------------------------------+----------------+----------------+
1 row in set (0.01 sec)

mysql> SELECT EVENT_NAME, WORK_COMPLETED, WORK_ESTIMATED FROM events_stages_current;
+------------------------------------------------------+----------------+----------------+
| EVENT_NAME                                           | WORK_COMPLETED | WORK_ESTIMATED |
+------------------------------------------------------+----------------+----------------+
| stage/innodb/alter table (read PK and internal sort) |          28060 |         268542 |
+------------------------------------------------------+----------------+----------------+
1 row in set (0.00 sec)

mysql> SELECT EVENT_NAME, WORK_COMPLETED, WORK_ESTIMATED FROM events_stages_current;
+------------------------------------------------------+----------------+----------------+
| EVENT_NAME                                           | WORK_COMPLETED | WORK_ESTIMATED |
+------------------------------------------------------+----------------+----------------+
| stage/innodb/alter table (read PK and internal sort) |          47972 |         268542 |
+------------------------------------------------------+----------------+----------------+
1 row in set (0.00 sec)

mysql> SELECT EVENT_NAME, WORK_COMPLETED, WORK_ESTIMATED FROM events_stages_current;
+---------------------------------------+----------------+----------------+
| EVENT_NAME                            | WORK_COMPLETED | WORK_ESTIMATED |
+---------------------------------------+----------------+----------------+
| stage/innodb/alter table (merge sort) |         138643 |         287346 |
+---------------------------------------+----------------+----------------+
1 row in set (0.01 sec)

mysql> SELECT EVENT_NAME, WORK_COMPLETED, WORK_ESTIMATED FROM events_stages_current;
+-----------------------------------+----------------+----------------+
| EVENT_NAME                        | WORK_COMPLETED | WORK_ESTIMATED |
+-----------------------------------+----------------+----------------+
| stage/innodb/alter table (insert) |         243821 |         287346 |
+-----------------------------------+----------------+----------------+
1 row in set (0.00 sec)

mysql> SELECT EVENT_NAME, WORK_COMPLETED, WORK_ESTIMATED FROM events_stages_current;
Empty set (0.00 sec)

終わるとevents_stages_currentからは消える。


The WORK_COMPLETED column shows the number of pages processed. The WORK_ESTIMATED column provides an estimate of the remaining work, in numbers of pages.

MySQL :: MySQL 5.7 Reference Manual :: 14.13.11.1 Monitoring ALTER TABLE Progress for InnoDB Tables Using Performance Schema

ということなので、work_completedとwork_estimatedはページ数らしい。


ちなみに、↑だとあまりにコピペでしかないのでちょっと気を利かせて終了予想時刻を計算するクエリー↓も書いたんですが


mysql> SELECT thread_id, event_name, sql_text, @progress:= (work_completed / work_estimated) * 100 AS progress, @elapsed:= (timer_current - timer_start) / power(10, 12) AS elapsed, @elapsed * (100 / @progress) - @elapsed AS estimated FROM (SELECT stage.thread_id, stage.event_name, work_completed, work_estimated, (SELECT timer_start FROM events_statements_current WHERE sql_text LIKE 'SELECT thread_id, event_name,%') AS timer_current, statement.timer_start, sql_text FROM events_stages_current AS stage JOIN events_statements_current AS statement USING(thread_id)) AS dummy;
+-----------+------------------------------------------------------+-------------------------------------+-------------+-------------+--------------------+
| thread_id | event_name                                           | sql_text                            | progress    | elapsed     | estimated          |
+-----------+------------------------------------------------------+-------------------------------------+-------------+-------------+--------------------+
|        28 | stage/innodb/alter table (read PK and internal sort) | ALTER TABLE t1 ADD UNIQUE KEY (val) | 7.330386000 | 1.877416142 | 23.734006530694177 |
+-----------+------------------------------------------------------+-------------------------------------+-------------+-------------+--------------------+
1 row in set (0.15 sec)

mysql> SELECT thread_id, event_name, sql_text, @progress:= (work_completed / work_estimated) * 100 AS progress, @elapsed:= (timer_current - timer_start) / power(10, 12) AS elapsed, @elapsed * (100 / @progress) - @elapsed AS estimated FROM (SELECT stage.thread_id, stage.event_name, work_completed, work_estimated, (SELECT timer_start FROM events_statements_current WHERE sql_text LIKE 'SELECT thread_id, event_name,%') AS timer_current, statement.timer_start, sql_text FROM events_stages_current AS stage JOIN events_statements_current AS statement USING(thread_id)) AS dummy;
+-----------+------------------------------------------------------+-------------------------------------+--------------+--------------+------------------+
| thread_id | event_name                                           | sql_text                            | progress     | elapsed      | estimated        |
+-----------+------------------------------------------------------+-------------------------------------+--------------+--------------+------------------+
|        28 | stage/innodb/alter table (read PK and internal sort) | ALTER TABLE t1 ADD UNIQUE KEY (val) | 46.969643300 | 33.385053874 | 37.6928839778295 |
+-----------+------------------------------------------------------+-------------------------------------+--------------+--------------+------------------+
1 row in set (0.01 sec)

mysql> SELECT thread_id, event_name, sql_text, @progress:= (work_completed / work_estimated) * 100 AS progress, @elapsed:= (timer_current - timer_start) / power(10, 12) AS elapsed, @elapsed * (100 / @progress) - @elapsed AS estimated FROM (SELECT stage.thread_id, stage.event_name, work_completed, work_estimated, (SELECT timer_start FROM events_statements_current WHERE sql_text LIKE 'SELECT thread_id, event_name,%') AS timer_current, statement.timer_start, sql_text FROM events_stages_current AS stage JOIN events_statements_current AS statement USING(thread_id)) AS dummy;
+-----------+---------------------------------------+-------------------------------------+--------------+--------------+--------------------+
| thread_id | event_name                            | sql_text                            | progress     | elapsed      | estimated          |
+-----------+---------------------------------------+-------------------------------------+--------------+--------------+--------------------+
|        28 | stage/innodb/alter table (merge sort) | ALTER TABLE t1 ADD UNIQUE KEY (val) | 50.831565800 | 40.169081343 | 38.854810033960106 |
+-----------+---------------------------------------+-------------------------------------+--------------+--------------+--------------------+
1 row in set (0.00 sec)

mysql> SELECT thread_id, event_name, sql_text, @progress:= (work_completed / work_estimated) * 100 AS progress, @elapsed:= (timer_current - timer_start) / power(10, 12) AS elapsed, @elapsed * (100 / @progress) - @elapsed AS estimated FROM (SELECT stage.thread_id, stage.event_name, work_completed, work_estimated, (SELECT timer_start FROM events_statements_current WHERE sql_text LIKE 'SELECT thread_id, event_name,%') AS timer_current, statement.timer_start, sql_text FROM events_stages_current AS stage JOIN events_statements_current AS statement USING(thread_id)) AS dummy;
+-----------+-----------------------------------+-------------------------------------+--------------+--------------+--------------------+
| thread_id | event_name                        | sql_text                            | progress     | elapsed      | estimated          |
+-----------+-----------------------------------+-------------------------------------+--------------+--------------+--------------------+
|        28 | stage/innodb/alter table (insert) | ALTER TABLE t1 ADD UNIQUE KEY (val) | 83.429283200 | 61.092267798 | 12.134140789914134 |
+-----------+-----------------------------------+-------------------------------------+--------------+--------------+--------------------+
1 row in set (0.00 sec)

だがしかしこれ、work_completedとwork_estimatedの値はALTER TABLE全体を通して全てのページ数を表現している(ので、途中でステージが変わってもprogressとして算出している値は常に進む)んだけど、各ステージごとの処理のスピードは違うので、そこまでアテにはならないかも知れない(が、それを言ったらSHOW ENGINE INNODB STATUSで見るのも似たようなもので。。)

体感ではステージがread PK and internal sort > merge sort >> insert >> その他 くらいの順で時間を占めているので、そのあたりを加味すればありかな。。


【2017/01/24 19:41】
これから2年、今では俺も @@pseudo_thread_id のことを知りました。

今ならこう書く。

SELECT 
  thread_id, 
  event_name, 
  sql_text, 
  @progress:= (work_completed / work_estimated) * 100 AS progress, 
  @elapsed:= (timer_current - timer_start) / power(10, 12) AS elapsed, 
  @elapsed * (100 / @progress) - @elapsed AS estimated 
FROM 
  (SELECT 
     stage.thread_id, 
     stage.event_name, 
     work_completed, 
     work_estimated, 
     (SELECT timer_start 
      FROM events_statements_current JOIN threads USING(thread_id)
      WHERE processlist_id = @@pseudo_thread_id) AS timer_current,
     statement.timer_start,
     sql_text
   FROM 
     events_stages_current AS stage JOIN events_statements_current AS statement USING(thread_id)
) AS dummy;

…あんま変わらんか。変わらんな。

MySQL 5.7.6でGTIDのローリング有効化ができるようになったので、システム全体を一度にシャットダウンしなくてもOK

MySQL 5.7.6メモそのいくつか。
今までgtid-mode= ONとOFFのマスター, スレーブは混在できなかったので、ONにするときは一度レプリケーション群を全部止めて起動しなおさなければいけなかった。それが、出来るようになったという話。

MySQL Bugs: #71543: A new GTID_MODE is needed to evaluate/migrate to GTID: ANONYMOUS_IN-GTID_OUT.
MySQL :: WL#7083: GTIDS: set gtid_mode=ON online


Percona Server 5.6.22では一足先にリリースされてましたね。やってることは同じだけど実装が違うっぽい予感。
Online GTID rollout now available in Percona Server 5.6



[root@f51faa7d23c3 ~]# mysqld --verbose --help | less
..
  --gtid-mode=name    Controls whether Global Transaction Identifiers (GTIDs)
                      are enabled. Can be OFF, OFF_PERMISSIVE, ON_PERMISSIVE,
                      or ON. OFF means that no transaction has a GTID.
                      OFF_PERMISSIVE means that new transactions (committed in
                      a client session using GTID_NEXT='AUTOMATIC') are not
                      assigned any GTID, and replicated transactions are
                      allowed to have or not have a GTID. ON_PERMISSIVE means
                      that new transactions are assigned a GTID, and replicated
                      transactions are allowed to have or not have a GTID. ON
                      means that all transactions have a GTID. ON is required
                      on a master before any slave can use
                      MASTER_AUTO_POSITION=1. To safely switch from OFF to ON,
                      first set all servers to OFF_PERMISSIVE, then set all
                      servers to ON_PERMISSIVE, then wait for all transactions
                      without a GTID to be replicated and executed on all
                      servers, and finally set all servers to GTID_MODE = ON.
..


( ´-`).oO(なんかmysqld --verbose --helpと ドキュメント で設定できる値が違うぞ。。正解はOFF, OFF_PERMISSIVE, ON_PERMISSIVE, ONの4つでした。mysqldが正解。


まずはフツーにgtid_mode= OFFでレプリケーションを組む。
この時点から既に、マスターでもスレーブでもバイナリーログに@@gtid_next= 'ANNONYMOUS'が出力されている。


[root@2d38323d5eb5 ~]# mysqlbinlog /usr/local/mysql/data/bin.000003
..
# at 360
#150217 19:17:16 server id 161  end_log_pos 425 CRC32 0xc1a16911        Anonymous_GTID  last_committed=1        sequence_numbe
r=2
SET @@SESSION.GTID_NEXT= 'ANONYMOUS'/*!*/;
# at 425
#150217 19:17:16 server id 161  end_log_pos 513 CRC32 0x3893ffcc        Query   thread_id=2     exec_time=0     error_code=0
SET TIMESTAMP=1424168236/*!*/;
create database d1
/*!*/;
..

[root@f51faa7d23c3 ~]# mysqlbinlog /usr/local/mysql/data/bin.000002
..
# at 360
#150217 19:17:16 server id 161  end_log_pos 425 CRC32 0x0f90114c        Anonymous_GTID  last_committed=1        sequence_numbe
r=2
SET @@SESSION.GTID_NEXT= 'ANONYMOUS'/*!*/;
# at 425
#150217 19:17:16 server id 161  end_log_pos 513 CRC32 0x3893ffcc        Query   thread_id=2     exec_time=0     error_code=0
SET TIMESTAMP=1424168236/*!*/;
create database d1
/*!*/;
..

コイツが緩衝材の役割を果たしてくれるっぽい。スレーブ側でOFF_PERMISSIVEに変更。


mysql> SELECT @@gtid_mode;
+-------------+
| @@gtid_mode |
+-------------+
| OFF         |
+-------------+
1 row in set (0.00 sec)

mysql> SET GLOBAL gtid_mode= 'OFF_PERMISSIVE';
Query OK, 0 rows affected (0.05 sec)

mysql> SELECT @@gtid_mode;
+----------------+
| @@gtid_mode    |
+----------------+
| OFF_PERMISSIVE |
+----------------+
1 row in set (0.00 sec)

この時点では目だった変化はない(CHANGE MASTER TO master_auto_position= 1もできない)し、スレーブにクエリーを発行しても


[root@f51faa7d23c3 ~]# mysqlbinlog /usr/local/mysql/data/bin.000003
..
# at 426
#150217 19:24:49 server id 162  end_log_pos 491 CRC32 0xedeaa731        Anonymous_GTID  last_committed=1        sequence_number=2
SET @@SESSION.GTID_NEXT= 'ANONYMOUS'/*!*/;
# at 491
#150217 19:24:49 server id 162  end_log_pos 603 CRC32 0xe284a3f5        Query   thread_id=6     exec_time=0     error_code=0
SET TIMESTAMP=1424168689/*!*/;
create database slave_only
..
/*!*/;

まだGTIDらしきものはバイナリーログに入っていない。


スレーブをON_PERMISSIVEに。


mysql> SET GLOBAL gtid_mode= 'ON_PERMISSIVE';
Query OK, 0 rows affected (0.03 sec)

mysql> SELECT @@gtid_mode;
+---------------+
| @@gtid_mode   |
+---------------+
| ON_PERMISSIVE |
+---------------+
1 row in set (0.00 sec)

mysql> DROP DATABASE slave_only;
Query OK, 0 rows affected (0.02 sec)

[root@f51faa7d23c3 ~]# mysqlbinlog /usr/local/mysql/data/bin.000004
..
# at 153
#150217 19:26:46 server id 162  end_log_pos 218 CRC32 0x9a581cd8        GTID    last_committed=0        sequence_number=1
SET @@SESSION.GTID_NEXT= '1e2c9249-b68b-11e4-ae86-0242ac1100a2:1'/*!*/;
# at 218
#150217 19:26:46 server id 162  end_log_pos 315 CRC32 0xb8794fef        Query   thread_id=11    exec_time=0     error_code=0
SET TIMESTAMP=1424168806/*!*/;
drop database slave_only
/*!*/;

スレーブで実行したクエリーにはGTIDが振られるようになった。マスターから流れてきたクエリーに対しては


mysql> INSERT INTO t1 VALUES (3, 'three');
Query OK, 1 row affected (0.01 sec)

[root@f51faa7d23c3 ~]# mysqlbinlog /usr/local/mysql/data/bin.000004
..
# at 1239
#150217 19:28:16 server id 161  end_log_pos 1304 CRC32 0x1fb41374       Anonymous_GTID  last_committed=5        sequence_number=6
SET @@SESSION.GTID_NEXT= 'ANONYMOUS'/*!*/;
# at 1304
#150217 19:28:16 server id 161  end_log_pos 1379 CRC32 0x7dd37323       Query   thread_id=8     exec_time=0     error_code=0
SET TIMESTAMP=1424168896/*!*/;
BEGIN
/*!*/;
# at 1379
#150217 19:28:16 server id 161  end_log_pos 1483 CRC32 0x2e8e2a4a       Query   thread_id=8     exec_time=0     error_code=0
SET TIMESTAMP=1424168896/*!*/;
INSERT INTO t1 VALUES (3, 'three')
/*!*/;
..

[root@f51faa7d23c3 ~]# mysqlbinlog /usr/local/mysql/data/bin.000004
..
# at 315
#150217 19:28:16 server id 161  end_log_pos 380 CRC32 0xbed7303c        Anonymous_GTID  last_committed=1        sequence_number=2
SET @@SESSION.GTID_NEXT= 'ANONYMOUS'/*!*/;
# at 380
#150217 19:28:16 server id 161  end_log_pos 455 CRC32 0x0dedb2e0        Query   thread_id=8     exec_time=0     error_code=0
SET TIMESTAMP=1424168896/*!*/;
BEGIN
/*!*/;
# at 455
#150217 19:28:16 server id 161  end_log_pos 559 CRC32 0x64c2139f        Query   thread_id=8     exec_time=0     error_code=0
use `d1`/*!*/;
SET TIMESTAMP=1424168896/*!*/;
INSERT INTO t1 VALUES (3, 'three')
/*!*/;
..

GTIDは振られない。うむうむ、いいんじゃないのこれ。
マスターも順番にOFF => OFF_PERMISSIVE => ON_PERMISSIVE => ONと続けてやると、


mysql> SET GLOBAL gtid_mode= OFF_PERMISSIVE;

# at 153
#150217 19:31:08 server id 161  end_log_pos 218 CRC32 0x0aec73f2        Anonymous_GTID  last_committed=0        sequence_numbe
r=1
SET @@SESSION.GTID_NEXT= 'ANONYMOUS'/*!*/;
# at 218
#150217 19:31:08 server id 161  end_log_pos 342 CRC32 0xb46d3cb9        Query   thread_id=9     exec_time=0     error_code=0
SET TIMESTAMP=1424169068/*!*/;
create database off_permissive
/*!*/;

mysql> SET GLOBAL gtid_mode= ON_PERMISSIVE;

# at 153
#150217 19:31:15 server id 161  end_log_pos 218 CRC32 0x261d23fa        GTID    last_committed=0        sequence_number=1
SET @@SESSION.GTID_NEXT= '7fea486e-b687-11e4-ae6f-0242ac1100a1:1'/*!*/;
# at 218
#150217 19:31:15 server id 161  end_log_pos 339 CRC32 0x3ab38b3d        Query   thread_id=9     exec_time=0     error_code=0
SET TIMESTAMP=1424169075/*!*/;
create database on_permissive
/*!*/;

mysql> SET GLOBAL gtid_mode= ON;

# at 193
#150217 19:31:32 server id 161  end_log_pos 258 CRC32 0x175fe321        GTID    last_committed=0        sequence_number=1
SET @@SESSION.GTID_NEXT= '7fea486e-b687-11e4-ae6f-0242ac1100a1:2'/*!*/;
# at 258
#150217 19:31:32 server id 161  end_log_pos 394 CRC32 0xd9ff015c        Query   thread_id=9     exec_time=0     error_code=0
SET TIMESTAMP=1424169092/*!*/;
create database gtid_is_on_at_last
/*!*/;

master_auto_position= 1にするためにはマスター側のgtid_modeがONであることと、スレーブ側のgtid_modeがOFF_PERMISSIVE以上であることが必要。
あと、OFF <=> OFF_PERMISSIVE <=> ON_PERMISSIVE <=> ON以外のパスでgtid_modeを設定しようとすると、


ERROR 1788 (HY000): The value of @@GLOBAL.GTID_MODE can only be changed one step at a time: OFF <-> OFF_PERMISSIVE <-> ON_PERMISSIVE <-> ON. Also note that this value must be stepped up or down simultaneously on all servers. See the Manual for instructions.

といって怒られる。



割と柔軟性があるつくりなので、きっちり順々に上げなくてもなんとかなった。
(スレーブOFF_PERMISSIVE => ON_PERMISSIVE, マスター OFF_PERMISSIVE => ON_PERMISSIVE => ON, スレーブ ONとかやっても動き続ける)

これ是非MySQL 5.6にもバックポートしてほしいですね。
5.6.23現在、@@global.gtid_modeはread_only variableなのでSET GLOBALで変更できない)

MySQL 5.7.6でCREATE USERせずにGRANTステートメントを叩くとワーニング

ワーニングが出るようになってますね。


「sql_modeのデフォルトにNO_AUTO_CREATE_USERを設定しようと思う」っていうネタがMorgan Tockerのブログにあがってましたのでその布石でしょうか。


mysql> SELECT @@sql_mode;
+---------------------------------------------------------------+
| @@sql_mode                                                    |
+---------------------------------------------------------------+
| ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION |
+---------------------------------------------------------------+
1 row in set (0.00 sec)

mysql> GRANT REPLICATION SLAVE ON *.* TO replicator IDENTIFIED BY 'replicator';
Query OK, 0 rows affected, 1 warning (0.00 sec)

mysql> SHOW WARNINGS;
+---------+------+------------------------------------------------------------------------------------------------------------------------------------+
| Level   | Code | Message                                                                                                                            |
+---------+------+------------------------------------------------------------------------------------------------------------------------------------+
| Warning | 1287 | Using GRANT for creating new user is deprecated and will be removed in future release. Create new user with CREATE USER statement. |
+---------+------+------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

mysql> SHOW GRANTS FOR replicator;
+----------------------------------------------------+
| Grants for replicator@%                            |
+----------------------------------------------------+
| GRANT REPLICATION SLAVE ON *.* TO 'replicator'@'%' |
+----------------------------------------------------+
1 row in set (0.00 sec)

出来てるには出来てるけど、SHOW GRANTSの結果にパスワードハッシュ出さなくなったのか。。


mysql> SELECT user, host, authentication_string FROM mysql.user WHERE user= 'replicator';
+------------+------+-------------------------------------------+
| user       | host | authentication_string                     |
+------------+------+-------------------------------------------+
| replicator | %    | *E6FA4B5E283079702D46597DFCD2766E7BF96B0E |
+------------+------+-------------------------------------------+
1 row in set (0.22 sec)

mysql.userテーブルにはちゃんと入ってる。

しかし 同じノリでproposalに上がってる NO_AUTO_VALUE_ON_ZEROの方は


mysql> CREATE TABLE t1 (num serial);
Query OK, 0 rows affected (0.02 sec)

mysql> INSERT INTO t1 VALUES (0);
Query OK, 1 row affected (0.01 sec)

mysql> SELECT * FROM t1;
+-----+
| num |
+-----+
|   1 |
+-----+
1 row in set (0.00 sec)

特に何のお咎めもなし。
どちらかというとこっちワーニング吐いてほしいんだけどな。。

NO_AUTO_VALUE_ON_ZEROが有効な状態だと


mysql> TRUNCATE t1;
Query OK, 0 rows affected (0.18 sec)

mysql> SET sql_mode= CONCAT_WS(',', @@sql_mode, 'NO_AUTO_VALUE_ON_ZERO');
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT @@sql_mode;
+-------------------------------------------------------------------------------------+
| @@sql_mode                                                                          |
+-------------------------------------------------------------------------------------+
| ONLY_FULL_GROUP_BY,NO_AUTO_VALUE_ON_ZERO,STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION |
+-------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

mysql> INSERT INTO t1 VALUES (0);
Query OK, 1 row affected (0.00 sec)

mysql> SELECT * FROM t1;
+-----+
| num |
+-----+
|   0 |
+-----+
1 row in set (0.00 sec)

とまあこんな風にauto_incのつもりでNULLじゃなく0を当てると本当に0が入ってしまうという(そんな風に書かれたコードが実在したとしたらこっちの方が余程影響でかい)
昔はauto_incrementなカラムに敢えてNULLじゃなくて0を渡すコードを 書いていた ことがあるんだけど、最近の人はどうなんだろう。

ちなみにNO_AUTO_CREATE_USERな状態でもIDENTIFIED BYがない場合にだけがエラーになるはずだから…と思ってたら。


mysql> SET sql_mode= CONCAT_WS(',', @@sql_mode, 'NO_AUTO_CREATE_USER');
Query OK, 0 rows affected, 1 warning (0.00 sec)

mysql> show warnings;
+---------+------+------------------------------------------------------------------------------------------------------+
| Level   | Code | Message                                                                                              |
+---------+------+------------------------------------------------------------------------------------------------------+
| Warning | 3090 | Setting sql mode 'NO_AUTO_CREATE_USER' is deprecated. It will be made read-only in a future release. |
+---------+------+------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

mysql> GRANT ALL on *.* to yoku;
ERROR 1133 (42000): Can't find any matching row in the user table

mysql> GRANT ALL on *.* to yoku@localhost identified by '0825';
Query OK, 0 rows affected, 1 warning (0.00 sec)

mysql> SHOW WARNINGS;
+---------+------+------------------------------------------------------------------------------------------------------------------------------------+
| Level   | Code | Message                                                                                                                            |
+---------+------+------------------------------------------------------------------------------------------------------------------------------------+
| Warning | 1287 | Using GRANT for creating new user is deprecated and will be removed in future release. Create new user with CREATE USER statement. |
+---------+------+------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

今のところ動作は変わってないけど、NO_AUTO_CREATE_USER自体が廃止されて、GRANTステートメントでCREATE USERされちゃうのは完全に廃止になるぽい。

MySQL 5.7.6は--secure-file-privを設定してないとWarningを吐くようになった

いいことだと思います :)

MySQL :: MySQL 5.7 Reference Manual :: 5.1.3 Server Command Options


2015-02-17T07:09:49.446585Z 0 [Warning] Insecure configuration for --secure-file-priv: Current value does not restrict locatio
n of generated files. Consider setting it to a valid, non-empty path.

ちなみにこのオプション、5.0.38からあるけど知名度が低い。いい加減、自分たちが何かアクションしないと誰も設定してくれないことに気付いたのかしら。。

MySQL :: MySQL 5.0 Reference Manual :: 5.1.3 Server Command Options


( ´-`).oO(前に どっか で書いたと思ってたけど全く触れてなかった。。

--secure-file-privの効能はFile_priv持ちのユーザーの操作を制限できて、


mysql56> SELECT @@global.secure_file_priv;
+---------------------------+
| @@global.secure_file_priv |
+---------------------------+
| NULL                      |
+---------------------------+
1 row in set (0.00 sec)

mysql56> SELECT LOAD_FILE('/etc/hosts');
..

mysql56> SELECT 1 INTO OUTFILE '/home/mysql/test.txt';
Query OK, 1 row affected (0.00 sec)

未設定の状態でFile_privがあると好き勝手できるのが、


mysql56> SELECT @@global.secure_file_priv;
+--------------------+
| @@secure_file_priv |
+--------------------+
| /tmp/              |
+--------------------+
1 row in set (0.00 sec)

mysql56> SELECT 1 INTO OUTFILE '/home/mysql/test.txt';
ERROR 1290 (HY000): The MySQL server is running with the --secure-file-priv option so it cannot execute this statement

mysql56> SELECT LOAD_FILE('/etc/hosts');
+-------------------------+
| LOAD_FILE('/etc/hosts') |
+-------------------------+
| NULL                    |
+-------------------------+
1 row in set (0.02 sec)

/tmp以外ではFile_privが制限されるような感じ。
これいいと思うんですけどねー。流行らない。なんでだ。

途中からMySQL 5.7.6関係なくなった。なんでだ。


【2015/03/20 12:31】
ワーニング吐くだけじゃなくて、rpmとかの場合は暗黙のデフォルトが設定されるようになってた。
日々の覚書: MySQL 5.7でLOAD DATA INFILEに失敗する時に疑うこと(--secure-file-privの暗黙のデフォルトが少し変わった)

MySQL 5.7.6でエラーコードが変わった件

MySQL 5.7.5と5.7.6をどこかに置いてdiffを取るのが便利。
コマンドはこんな感じ。


[root@v157-7-154-209 mysql]# diff -y -W 150 --suppress-common-lines 5.7.5/include/mysqld_error.h 5.7.6/include/mysqld_error.h | less
..

ざっと見、1885~のエラー番号がそのまま3000~に移された感じなので、もとのエラー番号に1115を足せば新しいエラー番号になりそう。1844まではエラー番号変わってない。

で、気になったのだけピックアップ。


* 1885
  * 5.7.5までER_FILE_CORRUPT
  * 5.7.6からER_SLAVE_HAS_MORE_GTIDS_THAN_MASTER

えっ、かぶせるの!?


* ER_SERVER_OFFLINE_MODE
  * 5.7.5まで1917
  * 5.7.6から3032

( ´-`).oO(日々の覚書: MySQL 5.7.5のオフラインモードはgraceful shutdownの夢を見るか の時は1917だったなぁ。。


* ER_QUERY_TIMEOUT
  * 5.7.5まで1909
  * 5.7.6から3024

使う気マンマンだったのでメモ。


diff取ってみる限り、GTID周りとかGIS周りとかBOOSTとかUSER_LOCKとかSLAVE_CHANNEL(マルチソースレプリケーションのアレだろう、たぶん)とか、新しいエラーばっかりだからそこまで既存の仕組みに影響はない…と…いいな。

あと、ER_GROUP_REPLICATION_* が新しく定義されててちょっと笑った。本当にやるつもりなのか…。