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

MySQL Routerつらくない(yumでインストールして動かしてみた編)

$
0
0
この記事は MySQL Fabric&Routerつらくない Advent Calendar 2015 の3日目です。

昨日の時点で、CentOS 6.6でMySQL Routerを試すには自前ビルドしないといけないことが判明しました。つらい だが俺にはDockerがある。CentOS 6.6の上でCentOS 7.1のコンテナー動かせばいいじゃん。systemdとか動かなそうだけど取り敢えず試すだけだからこういう時に便利ですよね! つらくない!


$ sudo docker run -it centos:centos7 bash
# yum install -y http://dev.mysql.com/get/mysql57-community-release-el7-7.noarch.rpm
# yum install -y mysql-router
..
Installed:
mysql-router.x86_64 0:2.0.2-1.el7

Complete!

お手軽! …って、mysql-routerの依存関係で引きずられてmysql-community-libsとか入るかと思ったけど入ってない。いいのかな。。


# rpm -ql mysql-router
/etc/mysqlrouter
/etc/mysqlrouter/mysqlrouter.ini
/usr/lib/systemd/system/mysqlrouter.service
/usr/lib/tmpfiles.d/mysqlrouter.conf
/usr/lib64/libmysqlharness.so
/usr/lib64/libmysqlharness.so.0
/usr/lib64/libmysqlrouter.so
/usr/lib64/libmysqlrouter.so.1
/usr/lib64/mysqlrouter
/usr/lib64/mysqlrouter/fabric_cache.so
/usr/lib64/mysqlrouter/keepalive.so
/usr/lib64/mysqlrouter/logger.so
/usr/lib64/mysqlrouter/routing.so
/usr/sbin/mysqlrouter
/usr/share/doc/mysql-router-2.0.2
/usr/share/doc/mysql-router-2.0.2/License.txt
/usr/share/doc/mysql-router-2.0.2/README.txt
/var/log/mysqlrouter
/var/run/mysqlrouter

# cat /usr/lib/systemd/system/mysqlrouter.service
[Unit]
Description=MySQL Router
After=syslog.target
After=network.target

[Service]
Type=simple
User=mysql
Group=mysql

PIDFile=/var/run/mysqlrouter/mysqlrouter.pid

ExecStart=/usr/sbin/mysqlrouter

PrivateTmp=true

[Install]
WantedBy=multi-user.target

rpmファイルの構成としては至極フツーな感じ。systemdもただ/usr/sbin/mysqlrouterしてるだけみたいだから取り敢えずやってみようかしら。


# mysqlrouter &
Logging to /var/log/mysqlrouter/mysqlrouter.log

# less /var/log/mysqlrouter/mysqlrouter.log
2015-12-02 05:13:52 INFO [7f9173b46700] keepalive started with interval 60
2015-12-02 05:13:52 INFO [7f9173b46700] keepalive
2015-12-02 05:14:52 INFO [7f9173b46700] keepalive
2015-12-02 05:15:52 INFO [7f9173b46700] keepalive

# ss -ltpn
State Recv-Q Send-Q Local Address:Port Peer Address:Port

あれ、どこのポートもLISTENしてない。
ドキュメントをナナメ読みしてみた感じ、どうも[routing:xxx]セクションに設定をしたぶんだけポートをLISTENするシロモノであるっぽい。


# vim /etc/mysqlrouter/mysqlrouter.ini
..
[routing:sakila_blue]
bind_port= 7001
mode= read-write
destinations= 127.0.0.1:3306

# mysqlrouter &
Logging to /var/log/mysqlrouter/mysqlrouter.log

# ss -ltpn
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 0 127.0.0.1:7001 *:* users:(("mysqlrouter",40,4))

# less /var/log/mysqlrouter/mysqlrouter.log
2015-12-02 15:23:48 INFO [7fae2cb46700] routing:sakila_blue started: listening on 127.0.0.1:7001; read-write
2015-12-02 15:23:48 INFO [7fae2d547700] keepalive started with interval 60
2015-12-02 15:23:48 INFO [7fae2d547700] keepalive

# vim /etc/mysqlrouter/mysqlrouter.ini
..
[routing:master]
bind_port= 7001
mode= read-write
destinations= 127.0.0.1:13454

[routing:slave]
bind_port= 7002
mode= read-only
destinations= 127.0.0.1:13455,127.0.0.1:13456

# pkill mysqlrouter
# mysqlrouter &
Logging to /var/log/mysqlrouter/mysqlrouter.log

# ss -ltpn
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 0 127.0.0.1:7001 *:* users:(("mysqlrouter",49,6))
LISTEN 0 0 127.0.0.1:7002 *:* users:(("mysqlrouter",49,4))

# less /var/log/mysqlrouter/mysqlrouter.log
2015-12-02 15:25:28 INFO [7f0b81e2b700] keepalive started with interval 60
2015-12-02 15:25:28 INFO [7f0b81e2b700] keepalive
2015-12-02 15:25:28 INFO [7f0b8142a700] routing:slave started: listening on 127.0.0.1:7002; read-only
2015-12-02 15:25:28 INFO [7f0b80a29700] routing:master started: listening on 127.0.0.1:7001; read-write

ふむふむ。
MySQL Proxyは1対多のプロキシしか構成できなかったけど、MySQL Routerだと多対多のプロキシが構成できるのね。


このコンフィグは組み込みでサポートしてる「Connection Routing」に当たるもので、MySQL Fabricは取り敢えず今日は関係なさげ。。見た目、Connector/Jのfailover記法みたいな感じで転けたら次のホストに行くって動作をさせられる様子。

MySQL :: MySQL Router :: 1.1.1 Connection Routing


* read-writeのポート

$ mysql -umsandbox -pmsandbox --protocol=tcp -P 7001 -e "SELECT @@port"
mysql: [Warning] Using a password on the command line interface can be insecure.
+--------+
| @@port |
+--------+
| 13454 |
+--------+

$ mysql -umsandbox -pmsandbox --protocol=tcp -P 7001 -e "SELECT @@port"
mysql: [Warning] Using a password on the command line interface can be insecure.
+--------+
| @@port |
+--------+
| 13454 |
+--------+

$ mysql -umsandbox -pmsandbox --protocol=tcp -P 7001 -e "SELECT @@port"
mysql: [Warning] Using a password on the command line interface can be insecure.
+--------+
| @@port |
+--------+
| 13454 |
+--------+

$ mysql -umsandbox -pmsandbox --protocol=tcp -P 7001 -e "SELECT @@port"
mysql: [Warning] Using a password on the command line interface can be insecure.
+--------+
| @@port |
+--------+
| 13454 |
+--------+


* read-onlyのポート

$ mysql -umsandbox -pmsandbox --protocol=tcp -P 7002 -e "SELECT @@port"
mysql: [Warning] Using a password on the command line interface can be insecure.
+--------+
| @@port |
+--------+
| 13455 |
+--------+

$ mysql -umsandbox -pmsandbox --protocol=tcp -P 7002 -e "SELECT @@port"
mysql: [Warning] Using a password on the command line interface can be insecure.
+--------+
| @@port |
+--------+
| 13456 |
+--------+

$ mysql -umsandbox -pmsandbox --protocol=tcp -P 7002 -e "SELECT @@port"
mysql: [Warning] Using a password on the command line interface can be insecure.
+--------+
| @@port |
+--------+
| 13455 |
+--------+

$ mysql -umsandbox -pmsandbox --protocol=tcp -P 7002 -e "SELECT @@port"
mysql: [Warning] Using a password on the command line interface can be insecure.
+--------+
| @@port |
+--------+
| 13456 |
+--------+

$ mysql -umsandbox -pmsandbox --protocol=tcp -P 7002 -e "SELECT @@port"
mysql: [Warning] Using a password on the command line interface can be insecure.
+--------+
| @@port |
+--------+
| 13455 |
+--------+

「read-writeは先頭のホストが失敗したら次のホストへ」
「read-onlyはラウンドロビン」
って書いてあった。ちゃんとドキュメントに書いてあるなんてつらくない!

MySQL :: MySQL Router :: 3.2.2 Connection Routing (Standalone)


マスターを落とすとこの通りフローティングしてくれて

$ ./master/stop

$ mysql -umsandbox -pmsandbox --protocol=tcp -P 7001 -e "SELECT @@port"
mysql: [Warning] Using a password on the command line interface can be insecure.
+--------+
| @@port |
+--------+
| 13455 |
+--------+

$ ./master/start

$ mysql -umsandbox -pmsandbox --protocol=tcp -P 7001 -e "SELECT @@port"
mysql: [Warning] Using a password on the command line interface can be insecure.
+--------+
| @@port |
+--------+
| 13455 |
+--------+

あれ、上げても勝手に戻るわけではないのか。

取り敢えず動いたので楽しい。つらくない!

MySQL Routerつらくない(CentOS 6.6でもビルドがしたい編)

$
0
0
この記事は MySQL Fabric&Routerつらくない Advent Calendar 2015の4日目です。

昨日の時点で、CentOS 7.1のコンテナーなら問題なくyumでインストールして動作確認ができることはわかりました。しかしまあ、勤め先の本番環境はほとんどがCentOS 6.xなので、どうせならそっちで動く形式にしておきたい(特に、MySQL RouterのスタイルからしてAPサーバーに載せるのが具合がいい気がするので)

SPECファイルの.inを見ている限り、systemdがない環境でもちゃんと選んでビルドしてくれそうではあったので頑張ってみることにした。

TL;DR


という訳で色々試してるんですが、rpmのビルドには失敗します。しょんぼり。誰か判ったら教えてください。バイナリー.tar.gzっぽいのはできたのでそろそろ諦めます。


CentOS 7.1ではビルドに成功する


$ rpm -i mysql-router-2.0.2-1.el7.src.rpm
$ cd rpmbuild/SPECS/
$ rpmbuild -bb mysql-router.spec
..
Checking for unpackaged file(s): /usr/lib/rpm/check-files /root/rpmbuild/BUILDROOT/mysql-router-2.0.2-1.el7.centos.x86_64
Wrote: /root/rpmbuild/RPMS/x86_64/mysql-router-2.0.2-1.el7.centos.x86_64.rpm
Wrote: /root/rpmbuild/RPMS/x86_64/mysql-router-debuginfo-2.0.2-1.el7.centos.x86_64.rpm
Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.cku59B
+ umask 022
+ cd /root/rpmbuild/BUILD
+ cd mysql-router-2.0.2
+ rm -rf /root/rpmbuild/BUILDROOT/mysql-router-2.0.2-1.el7.centos.x86_64
+ exit 0

まあ、当然ですが上手くいきました。


CentOS 6.6だとダメ


$ rpm -i mysql-router-2.0.2-1.el7.src.rpm
$ cd rpmbuild/SPECS/
$ scl enable devtoolset-2 "rpmbuild -ba mysql-router.spec"
..
Processing files: mysql-router-2.0.2-1.el6.x86_64
error: File not found: /root/rpmbuild/BUILDROOT/mysql-router-2.0.2-1.el6.x86_64/etc/init.d/mysqlrouter
Executing(%doc): /bin/sh -e /var/tmp/rpm-tmp.q5xdpa
+ umask 022
+ cd /root/rpmbuild/BUILD
+ cd mysql-router-2.0.2
+ DOCDIR=/root/rpmbuild/BUILDROOT/mysql-router-2.0.2-1.el6.x86_64/usr/share/doc/mysql-router-2.0.2
+ export DOCDIR
+ rm -rf /root/rpmbuild/BUILDROOT/mysql-router-2.0.2-1.el6.x86_64/usr/share/doc/mysql-router-2.0.2
+ /bin/mkdir -p /root/rpmbuild/BUILDROOT/mysql-router-2.0.2-1.el6.x86_64/usr/share/doc/mysql-router-2.0.2
+ cp -pr License.txt README.txt /root/rpmbuild/BUILDROOT/mysql-router-2.0.2-1.el6.x86_64/usr/share/doc/mysql-router-2.0.2
+ exit 0


RPM build errors:
File not found: /root/rpmbuild/BUILDROOT/mysql-router-2.0.2-1.el6.x86_64/etc/init.d/mysqlrouter

明らかにsystemdを使わない判定の方に転がって、sysvinitスタイルのものが見付からないとかなんとか言ってる。
ソースを落としてみるとpackagingディレクトリーにbuild_rpm.shとかいうのもあってそっちも試してみたけれど、なんかこっちは最終的にmysql-router-commercial-*.tar.gzを要求されてビルドが転ける。


仕方がないのでバイナリー.tar.gz版ぽいものをビルド


$ scl enable devtoolset-2 "cmake -DINSTALL_LAYOUT=STANDALONE -DCMAKE_INSTALL_PREFIX=/usr/local/mysql-router"
$ make
$ VERBOSE=1 sudo make install
..
Install the project...
/usr/bin/cmake -P cmake_install.cmake
-- Install configuration: ""
-- Up-to-date: /usr/local/mysql-router/include/mysql/mysqlrouter/config_parser.h
-- Up-to-date: /usr/local/mysql-router/include/mysql/mysqlrouter/plugin.h
-- Up-to-date: /usr/local/mysql-router/include/mysql/mysqlrouter/filesystem.h
-- Up-to-date: /usr/local/mysql-router/include/mysql/mysqlrouter/loader.h
-- Up-to-date: /usr/local/mysql-router/lib/libmysqlharness.a
-- Installing: /usr/local/mysql-router/lib/libmysqlharness.so.0
-- Up-to-date: /usr/local/mysql-router/lib/libmysqlharness.so
-- Installing: /usr/local/mysql-router/lib/mysqlrouter/logger.so
-- Up-to-date: /usr/local/mysql-router/include/mysql/mysqlrouter/logger.h
-- Installing: /usr/local/mysql-router/lib/mysqlrouter/keepalive.so
-- Installing: /usr/local/mysql-router/lib/mysqlrouter/fabric_cache.so
-- Up-to-date: /usr/local/mysql-router/include/mysql/mysqlrouter/fabric_cache.h
-- Installing: /usr/local/mysql-router/lib/mysqlrouter/routing.so
-- Up-to-date: /usr/local/mysql-router/include/mysql/mysqlrouter/routing.h
-- Installing: /usr/local/mysql-router/docs/README.txt
-- Installing: /usr/local/mysql-router/docs/License.txt
-- Installing: /usr/local/mysql-router/docs/sample_mysqlrouter.ini
-- Up-to-date: /usr/local/mysql-router/include/mysql/mysqlrouter/datatypes.h
-- Up-to-date: /usr/local/mysql-router/include/mysql/mysqlrouter/utils.h
-- Up-to-date: /usr/local/mysql-router/include/mysql/mysqlrouter/plugin_config.h
-- Installing: /usr/local/mysql-router/bin/mysqlrouter
-- Installing: /usr/local/mysql-router/lib/libmysqlrouter.so.1
-- Up-to-date: /usr/local/mysql-router/lib/libmysqlrouter.so

これで/usr/lcoal/mysql-routerの下に全部押し込められた気がする。
他のコンテナーにコピーして展開してみる。


$ tar -C /usr/local -cf mysql-router-2.0.2.glibc12.tar.gz mysql-router/
$ docker cp build:/root/mysql-router-2.0.2.glibc12.tar.gz ./
$ docker run -v /home/yoku0825/mysql-router-2.0.2.glibc12.tar.gz:/root/mysql-router-2.0.2.glibc12.tar.gz -it centos:centos6.6 bash
# /usr/local/mysql-router/bin/mysqlrouter
bin/mysqlrouter: error while loading shared libraries: libmysqlrouter.so.1: cannot open shared object file: No such file or directory
# LD_LIBRARY_PATH=/usr/local/mysql-router/lib /usr/local/mysql-router/bin/mysqlrouter --help
Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Start MySQL Router.

Configuration read from the following files in the given order (enclosed
in parentheses means not available for reading):
(./mysqlrouter.ini)
(/root/.mysqlrouter.ini)

Usage: mysqlrouter [-v|--version] [-h|--help]
[-c|--config=]
[-a|--extra-config=]

Options:
-v, --version
Display version information and exit.
-h, --help
Display this help and exit.
-c , --config
Only read configuration from given file.
-a , --extra-config
Read this file after configuration files are read from either
default locations or from files specified by the --config
option.

ライブラリーパスさえ通せば動きそうだった。つらくない!

MySQL 5.5とそれ以前のマスターにMySQL 5.7のスレーブはぶら下げられない

$
0
0
出来ないのは仕様だそうです。ステータスは"Not a Bug"

MySQL Bugs: #79272: MySQl 5.5 master fails to replicate to MySQL 5.7.9


これはレプリケーションの開始時にI/Oスレッドがマスターのserver_uuidサーバー変数を参照しに行くんだけど、その値が取れなくてエラーになる。

…あれ、これどこかで聞いたことある気がする。と思ったら5.6の時に軽く調べてた。
日々の覚書: MySQL 5.6より前のマスターにMySQL 5.6のスレーブをぶら下げるとワーニングが出る(Err: 1193)

MySQL 5.6からMySQL 5.5は同じくserver_uuid変数を参照するものの、I/Oスレッドはアボートせずワーニング止まりだった。それがMySQL 5.7では止まる。

試しにMySQL::Sandboxで5.5.46, 5.6.27, 5.7.9を浮かしてみると

mysql [localhost] {msandbox} ((none)) > CHANGE MASTER TO master_host= '127.0.0.1', master_port= 5627, master_user= 'rsandbox', master_password= 'rsandbox', master_log_file= 'bin.000001', master_log_pos= 1 FOR CHANNEL 'mysql56';
Query OK, 0 rows affected, 2 warnings (0.01 sec)

mysql [localhost] {msandbox} ((none)) > CHANGE MASTER TO master_host= '127.0.0.1', master_port= 5546, master_user= 'rsandbox', master_password= 'rsandbox', master_log_file= 'bin.000001', master_log_pos= 1 FOR CHANNEL 'mysql55';
Query OK, 0 rows affected, 2 warnings (0.01 sec)

mysql [localhost] {msandbox} ((none)) > START SLAVE;
Query OK, 0 rows affected (0.01 sec)

mysql [localhost] {msandbox} (performance_schema) > SELECT * FROM performance_schema.replication_connection_status;
+--------------+------------+--------------------------------------+-----------+---------------+---------------------------+--------------------------+--------------------------+-------------------+--------------------------------------------------------------------------------------------------------------------------------------------------+----------------------+
| CHANNEL_NAME | GROUP_NAME | SOURCE_UUID | THREAD_ID | SERVICE_STATE | COUNT_RECEIVED_HEARTBEATS | LAST_HEARTBEAT_TIMESTAMP | RECEIVED_TRANSACTION_SET | LAST_ERROR_NUMBER | LAST_ERROR_MESSAGE | LAST_ERROR_TIMESTAMP |
+--------------+------------+--------------------------------------+-----------+---------------+---------------------------+--------------------------+--------------------------+-------------------+--------------------------------------------------------------------------------------------------------------------------------------------------+----------------------+
| mysql55 | | | NULL | OFF | 0 | 0000-00-00 00:00:00 | | 1593 | Fatal error: The slave I/O thread stops because a fatal error is encountered when it tries to get the value of SERVER_UUID variable from master. | 2015-12-08 15:54:40 |
| mysql56 | | 9b2a8e7a-9d57-11e5-8f87-02018582356a | 42 | ON | 2 | 2015-12-08 15:55:40 | | 0 | | 0000-00-00 00:00:00 |
+--------------+------------+--------------------------------------+-----------+---------------+---------------------------+--------------------------+--------------------------+-------------------+--------------------------------------------------------------------------------------------------------------------------------------------------+----------------------+
2 rows in set (0.00 sec)


5.7と5.5は確かにエラった。

5.6から5.5だと

mysql [localhost] {msandbox} ((none)) > CHANGE MASTER TO master_host= '127.0.0.1', master_port= 5546, master_user= 'rsandbox', master_password= 'rsandbox', master_log_file= 'bin.000001', master_log_pos= 1;
Query OK, 0 rows affected, 2 warnings (0.02 sec)

mysql [localhost] {msandbox} ((none)) > START SLAVE;
Query OK, 0 rows affected (0.02 sec)

mysql [localhost] {msandbox} ((none)) > SHOW SLAVE STATUS\G
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: 127.0.0.1
Master_User: rsandbox
Master_Port: 5546
Connect_Retry: 60
Master_Log_File: bin.000001
Read_Master_Log_Pos: 107
Relay_Log_File: mysql_sandbox5627-relay-bin.000002
Relay_Log_Pos: 264
Relay_Master_Log_File: bin.000001
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
..

エラーにはならない。コードを眺めてみると、5.6では "SHOW VARIABLES LIKE 'SERVER_UUID'"して、ちゃんとserver_uuidが返ってくる(server_uuidをサポートしている5.6以上)かエラーになる(予期しない想定外の事態なんだと思う)か空の結果セットが返ってくる(5.5とそれより前の、server_uuidをサポートしていないバージョン)かの3択で切り分けていた。

https://github.com/mysql/mysql-server/blob/12ec343846f2c1ed9f0454ccca8997afc4a0bb5f/sql/rpl_slave.cc#L1877-L1880


それが5.7だと"SELECT @@GLOBAL.SERVER_UUID"で取ろうとするので、server_uuidをサポートしていないバージョンだと"Unknown system variable"で一発エラーになってFatalでI/Oスレッドが止まる。

mysql [localhost] {msandbox} ((none)) > SELECT @@GLOBAL.SERVER_UUID;
ERROR 1193 (HY000): Unknown system variable 'SERVER_UUID'

https://github.com/mysql/mysql-server/blob/ef4fcf760a2d3b098a475323e289a6cab57020ab/sql/rpl_slave.cc#L2356-L2358


という訳で、MySQL 5.7のmysql_upgradeでイケるにしてもレプリケーションに属するサーバーを全部いっぺんに上げない限りは5.5から5.7へはジャンプできないのでした(´・ω・`)

pt-online-schema-changeと5.6 InnoDBのオンラインALTER TABLE使い分け

$
0
0
この記事は MySQL Casual Advent Calendar 2015 の9日目です。

MySQL 5.6から InnoDBのオンラインDDL が導入されて久しいですが、一方で pt-online-schema-change (以下pt-osc)もまだまだ元気です。MySQL 5.5とそれより前ではpt-osc一択になりますが、MySQL 5.6とそれ以上の場合はInnoDBさんに任せるかpt-oscを使うかを選択することができます。

MySQL 5.6でもpt-osc一択にしても構わないといえば構わないんですが、いくつかのケースではInnoDBさんに任せた方が速くなったり安定したりするので、そのあたり解説していきます。


TL;DR

ウチの使い分け。
  • 原則 pt-osc
  • スレーブの台数が多すぎない かつ
    • データ容量が馬鹿でかくてストレージ食いつぶしそう または
    • INSERT大杉で2度のメタデータロックが馬鹿にならない または
    • デッドロック大杉 なら
    • InnoDB Online DDLでRSU(Rolling Schema Upgrade)

When pt-osc?

  • pt-oscの仕組みをざっくり
    • 元テーブルから新しいテーブル(空っぽ)を作って
    • 空っぽのテーブルにALTER TABLEをかけて
    • 元テーブルへの更新をトリガーでフックしながら古いデータをコピーする
    • コピーが終わったらRENAME TABLEで新旧テーブルを入れ替える
  • 古いデータをコピーする処理を細切れにしてくれるので、レプリケーションスレーブを詰まらせることが少ない
    • あと、スレーブの遅延監視のための --recursion-methodが結構柔軟に設定できるので、「MySQL 5.6だからオンラインALTER TABLEいけるじゃん?」「ざんねん! SQLスレッドは同時に1つのクエリーしかさばけない!」ということはない
  • 古いデータをコピーする処理が細切れなので、
    • デッドロックがボコボコ出たりする
    • binlogを抱いて溺死
  • まるまるテーブルをコピーするので
    • 容量に十分な余裕が必要
    • バッファプールもそれなりに圧迫する
    • binlogを抱いて(ry
  • トリガーを張る時、RENAME TABLEのタイミングでメタデータロックを取る
    • 同時アクセス(メタデータロックなのでSELECTも含む)が多い環境だと割と簡単に詰まる
    • 先行トランザクションが終了するまでメタデータロック待ちするので、その更に後から来たクエリーはメタデータロックが取れるまで待たされる
      • trx1> SELECT .. -- コイツが終わらないとき
      • trx2> RENAME TABLE .. -- コイツが"Waiting for table metadata lock"になり
      • trx3> SELECT .. -- コイツも"Waiting for table metadata lock"になる
    • メタデータロックとHandlerSocket Pluginの相性が最悪
    • pt-osc開始時のメタデータロックはまだ「様子を見ながら開始する」「引っかかったら即中断」することができるけど、終了直前のは祈るしかない(;-人-)
      • 中断した場合、一時テーブルとトリガーのお片付けは自分でやる必要がある
        • 先にDROP TABLEするとトリガーが転け続けるので必ずDROP TRIGGERから先にやること
  • 入れ替えたあとに要らなくなった方のDROP TABLEが走るので、 lazy drop tableを食らったことがある
    • lazy drop tableは直ったことになってるんだっけ?
  • テーブルがまるまる再作成されるので、ついでにOPTIMIZEがかかったようなもの

When InnoDB Online DDL?

  • InnoDBオンラインDDLの仕組みをざっくり
    • ALTER TABLEで追加するインデックス, カラムなどを先に.ibdの外側に作っておくイメージ
      • ソートやもろもろ終わってから、.ibdファイルにマージする感じ
    • 追加するインデックスやカラムに対するALTER TABLE中の更新はtmpdirに書き出しておいて後からマージ
  • マスターで1時間かかったALTER TABLEがスレーブでも1時間かかるのはブロッキングなALTER TABLEと同じ
    • スレーブの更新クエリーはSQL_threadからしか入ってこないので、SQL_threadがALTER TABLEを掴みっぱなしになって結局レプリケーションが遅れる
    • マスターとスレーブで *レプリケーションを通さずに*それぞれオンラインALTER TABLEをかけることで回避する
      • この手間が惜しい場合はこっちは使えない
      • Rolling Schema Upgrade(RSU)って言うらしい
  • テーブル全体のコピーが発生しないのでI/O量がpt-oscに比べて少ない
    • とはいえそれなり(もとのFast Index Creation相当)のI/Oは発生する
    • binlogにやさしい
  • カラムのデータを読み取る処理はロックを取らないのでデッドロックは起こらない
  • メタデータロックに関しては開始時と終了時らしい
    • 開始時のメタデータロックに関する注意事項はpt-oscと同じ。長時間トランザクションが来てないタイミングを見計らって開始
    • 終了時に.ibdファイルにもろもろマージするタイミングでもメタデータロックを取るらしいけれど、pt-oscと違って目に見えた範囲で問題になったことはない
      • pt-oscはステートメントでロックを取るステートメントを実行するのに対し、内部のAPIでロックを取るからなのかしらん?
  • オペレーションを選ぶ。たとえばALTER TABLE .. MODIFYでデータ型が変わるものはブロッキングなALTER TABLEになる
  • ポイントインタイムリカバリー(PITR)ととても相性が悪い
    • RSUでない場合、単純にmysqlbinlogの結果を食わせる場合に結局そのスレッドを占有してしまう。
    • *RSUでbinlogに書き出させない場合、このALTER TABLEだけPITRできなくなってしまう。*

という訳で、InnoDBのオンラインALTER TABLEでRSUするやり方。


master> SET SESSION sql_log_bin= 0;
master> ALTER TABLE ..;

slave1> SET SESSION sql_log_bin= 0; -- binlog吐いてるなら。中間マスターでない限り必須ではない。好み。
slave1> ALTER TABLE ..;

slave1> SET SESSION sql_log_bin= 0; -- 同上
slave1> ALTER TABLE ..;

「やり方」も何もとても簡単。そのALTER TABLEだけbinlogに出力させないようにして、 *マスターとスレーブ全てのサーバーで* ALTER TABLEを実行する。

この方法だとbinlogに一切合財ALTER TABLEの情報が載らなくなるので、このALTER TABLEをまたぐ期間のPITRができなくなる。RSU後にフルバックアップ推奨。
(インデックス追加くらいなら後から来たクエリーも特に問題ないけど、カラム追加だとアプリのリリースかけた後のクエリーが全部詰まって死ぬ)
なので運用上は *原則pt-osc* としています。何回かやってもpt-oscに失敗する(あるいは容量不足でpt-oscができないことが明白)な場合だけRSU。


用法、用量を守って使い分けると便利です。

sqlplusのお供、rlwrapのオプションについて調べてみた

$
0
0
この記事は JPOUG Advent Calendar 2015 の10日目の記事です。

残念ながら私は全くOracleというDBMSが使えません。どれくらい使えないかというと、

SQL> SHOW DATABASES;
SP2-0158: unknown SHOW option "DATABASES"

( ゚д゚)ノ せんせー、このDBMSダメなやつです!

というくらい使えません。PostgreSQLだったらまだコマンドラインクライアントをゴニョって`SHOW DATABASES`で一覧を出すくらいのことは出来たんですが、sqlplusだとそういう訳にも行きませんね、このクローズドソース野郎!!1

という訳で、sqlplus使い御用達(と聞いている) rlwrapの話をしようと思います。わたしは groonga (コマンドラインクライアントの方)でrlwrapをよく使いますが、初めてオプションを調べてみたので記録しておきます。

有名な話だったらごめんなさい。


まずは単に起動。

$ rlwrap sqlplus

SQL*Plus: Release 11.2.0.4.0 Production on Mon Dec 7 18:52:56 2015

Copyright (c) 1982, 2013, Oracle. All rights reserved.

Enter user-name: supersuper
Enter password: **********


とまあこの通りパスワード部分はreadlineを通らず、"*"でマークされました。
Enter叩いた瞬間に"*"も消えました。sqlplus、こんな動作なのか。

そしてそこで有名なやつとして-aオプションを足してみると

$ rlwrap -a sqlplus

SQL*Plus: Release 11.2.0.4.0 Production on Mon Dec 7 18:59:45 2015

Copyright (c) 1982, 2013, Oracle. All rights reserved.

Enter user-name: supersuper
Enter password: top_s3cret


如何にもありそうな感じで、剥き出しでパスワードが表示される。本来、パスワードっぽいものを遠慮してreadlineに流さずにやってるのを、全部readlineに通してそのあとNL契機でsqlplusに渡してるんですね、たぶん。"*"の時と同じくエンター叩いた瞬間に消えますが、SQLのプロンプトで↑キーを押すと


SQL> top_s3cret

履歴に出た━━━━(゚∀゚)━━━━!!
readlineに渡るとhistory_fileにも書くからか。そうか、確かにそうだけど面白い。


$ rlwrap -a"Enter password:" sqlplus

SQL*Plus: Release 11.2.0.4.0 Production on Mon Dec 7 19:02:19 2015

Copyright (c) 1982, 2013, Oracle. All rights reserved.

Enter user-name: supersuper
Enter password: **********

-aオプションの直後(スペース文字を入れると、「スペース入れるな!」って怒られた)に指定した文字列が来た時は、パスワードだと見做してreadlineを通さなくなる、様子。
ちなみに"Enter password:"の状態で-aオプション *無し*の時でも *↑↓キーによる履歴呼び出しが可能*なので、一度パスワードが記録されると何となく雰囲気でログインできるかも知れない。


$ rlwrap -p"YELLOW" sqlplus

SQL*Plus: Release 11.2.0.4.0 Production on Mon Dec 7 19:15:38 2015

Copyright (c) 1982, 2013, Oracle. All rights reserved.

Enter user-name: supersuper
Enter password:

う、テキストべた張りだと感動が伝わらないと思いますが、プロンプトが全て黄色太文字になってます。
"yellow"だと太文字ではない様子。"RED"だと赤太文字、"red"だと赤細文字でした。へーすごい。MySQLでいうところの--pagerにカラーコード仕込む感じですね。でもこっちの方が英語で指定できていいなぁ。ってかmysqlコマンドラインクライアントもrlwrap経由で起動させてやれば 日々の覚書: MySQL 5.6以降のmysqlコマンドラインクライアントでプロンプトに色を付けるcmakeオプション なんてのも必要なくなりそう。うむ。


$ rlwrap -z"pipeto" sqlplus
..
SQL> SELECT * FROM ..; | less -S -r

そして極め付け。SQLの結果をそのまま別のプロセスにパイプで渡せるpipetoフィルター。mysqlにはpagerコマンドがあるので気楽にlessに食わせられるけれど、sqlplusにはお手軽なのがないらしい。

`-z"pipeto"`オプションを付けると、出力をパイプに向けられる。
( *出力を* 向けるので、パイプの手前までで出力が開始されてないとダメ。つまりSQLなら *セミコロンまで打ち込んでからパイプを付ける* 

ところでこれ、MySQLは文字列結合に"||"演算子を使わないから良いとして、Oracleはよく使いそう(偏見)なので、上手くやってくれ…

SQL> SELECT 'a' || 'b'; | wc

sh: -c: line 0: 期待してない token `|'のあたりにシンタックスエラー
sh: -c: line 0: `| 'b'; | wc'

2

る訳がなかった。そりゃ別にsqlplus用のユーティリティーってわけじゃあるまいし。
幸いにしてフィルターの類はPerlで書かれているらしいので、ちょっと探してみる。


$ rpm -ql rlwrap
/usr/bin/rlwrap
/usr/share/doc/rlwrap-0.41
/usr/share/doc/rlwrap-0.41/AUTHORS
/usr/share/doc/rlwrap-0.41/COPYING
/usr/share/doc/rlwrap-0.41/NEWS
/usr/share/doc/rlwrap-0.41/README
/usr/share/man/man1/rlwrap.1.gz
/usr/share/man/man3/RlwrapFilter.3pm.gz
/usr/share/rlwrap
/usr/share/rlwrap/completions
/usr/share/rlwrap/completions/coqtop
/usr/share/rlwrap/completions/testclient
/usr/share/rlwrap/filters
/usr/share/rlwrap/filters/README
/usr/share/rlwrap/filters/RlwrapFilter.3pm
/usr/share/rlwrap/filters/RlwrapFilter.pm
/usr/share/rlwrap/filters/censor_passwords
/usr/share/rlwrap/filters/count_in_prompt
/usr/share/rlwrap/filters/ftp_filter
/usr/share/rlwrap/filters/history_format
/usr/share/rlwrap/filters/listing
/usr/share/rlwrap/filters/logger
/usr/share/rlwrap/filters/null
/usr/share/rlwrap/filters/paint_prompt
/usr/share/rlwrap/filters/pipeline
/usr/share/rlwrap/filters/pipeto
/usr/share/rlwrap/filters/scrub_prompt
/usr/share/rlwrap/filters/simple_macro
/usr/share/rlwrap/filters/template
/usr/share/rlwrap/filters/unbackspace

$ vim /usr/share/rlwrap/filters/pipeto
..
50 sub input {
51 my $input;
52 $raw_input = $_;
53 ($input, undef, $pipeline) = /([^|]*)(\|(.*))?/;
54 return $input;
55 }
..

ここで切り分けているぽい。最初のパイプの手前までが元のコマンド、パイプの後ろがフィルターになってる。
そこでこんなパッチにしてみました。


*** /usr/share/rlwrap/filters/pipeto.orig       2010-01-03 21:42:16.000000000 +0900
--- /usr/share/rlwrap/filters/pipeto 2015-12-08 10:58:54.817411488 +0900
***************
*** 53 ****
! ($input, undef, $pipeline) = /([^|]*)(\|(.*))?/;
--- 53,54 ----
! ($input, $pipeline) = /(.+)?\s+\|\s+(.*)?/;
! $input= $_ unless $input;

" | "(空白、パイプ、空白。空白は1つ以上の任意の個数でOK)をパイプと認識させるように変更。


SQL> SELECT 'a' || 'b' FROM dual /* パイプを通る */; | less
'A
--
ab

SQL> SELECT 'a' || 'b' FROM dual /* パイプを通らない */; |less
2

パイプを通らない場合、"|less"までをsqlplusが解釈しようとするのでちゃんと出力されませんね(これは-z"pipeto"を取ってもそんな動きだった)


ざっとマニュアルを読んで面白そうだったものの紹介でした!
(個人的にはパスワードプロンプトで↑↓使えたのが面白かった)

MySQL Routerつらくない(MySQL Fabricと組み合わせて使ってみる編)

$
0
0
この記事は MySQL Fabric&Routerつらくない Advent Calendar 2015の10日目のはずだったけれど日付が変わっちゃった残念な記事です(´・ω・`)


$ mysqlfabric --param=protocol.xmlrpc.address=172.17.3.202:32274 group lookup_servers myfabric
Fabric UUID: 5ca1ab1e-a007-feed-f00d-cab3fe13249e
Time-To-Live: 1

server_uuid address status mode weight
------------------------------------ ----------------- --------- ---------- ------
7dbe3da8-9f4f-11e5-ad3e-0242ac1103ce 172.17.3.206:3306 SECONDARY READ_ONLY 1.0
81392c70-9f4f-11e5-ad15-0242ac1103d0 172.17.3.208:3306 SECONDARY READ_ONLY 1.0
84d6578f-9f4f-11e5-ad3b-0242ac1103d2 172.17.3.210:3306 PRIMARY READ_WRITE 1.0

まずはこんなレプリケーションを組んでMySQL Fabricに登録させておく。
MySQL FabricのIPアドレスは172.17.3.202。MySQL RouterのIPアドレスは172.17.3.218。
MySQL Fabric上のグループ名はmyfabric。


[fabric_cache:dummy]
address = 172.17.3.202
user = admin

[routing:master]
bind_address= 0.0.0.0:13306
mode = read-write
destinations= fabric+cache://dummy/group/myfabric

[routing:slave]
bind_address= 0.0.0.0:23306
mode = read-only
destinations= fabric+cache://dummy/group/myfabric

MySQL Routerに追加したコンフィグはこんな感じ。
日々の覚書: MySQL Routerつらくない(yumでインストールして動かしてみた編) の時にはIPアドレスを直書きしていたdestinationsに、 "fabric+cache://.."でFabric Cacheプラグインを指定できる。

まずはfabric_cache:* セクションでMySQL Fabricサーバーの情報を定義する。コロンのあとはMySQL Fabricを一意に識別する識別子になる。今回はdummyにしてみた。
addressはMySQL FabricサーバーのIPアドレス。特別指定してない場合はポート32275(MySQL FabricがMySQLプロトコルを喋る方のポート)に接続しに行く。
userはMySQLサーバー側のユーザーではなく、MySQL Fabricのユーザー(`mysqlfabric manage setup`した時に聞かれるやつ)

routing:*セクションで前回と変わってるのはdestinationsのところだけ。"fabric+cache://"は決め打ち、"dummy"の部分がfabric_cache:*セクションで命名した名前、"group"は今のところ決め打ち(たぶん、`mysqlfabric group lookup_servers myfabric` のgroupだと思う)、最後がMySQL Fabric上のグループ名。今回はmyfabric。


$ mysql -h 172.17.3.218 -P 13306 -u ap

mysql> SELECT current_user();
+----------------+
| current_user() |
+----------------+
| ap@% |
+----------------+
1 row in set (0.00 sec)

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

MySQL Fabricの向こう側、実際のMySQLサーバーに接続するためのユーザー名はクライアント側が指定する。


$ mysql -h 172.17.3.218 -P 23306 -u ap -e "SELECT @@hostname"
+--------------+
| @@hostname |
+--------------+
| 3a72ab61519c |
+--------------+

$ mysql -h 172.17.3.218 -P 23306 -u ap -e "SELECT @@hostname"
+--------------+
| @@hostname |
+--------------+
| 548859aa0c72 |
+--------------+

$ mysql -h 172.17.3.218 -P 23306 -u ap -e "SELECT @@hostname"
+--------------+
| @@hostname |
+--------------+
| 3a72ab61519c |
+--------------+

$ mysql -h 172.17.3.218 -P 23306 -u ap -e "SELECT @@hostname"
+--------------+
| @@hostname |
+--------------+
| 548859aa0c72 |
+--------------+

スレーブ側に割り当てたポート23306にアクセスすれば、ラウンドロビン。

え、なんかこれホントにつらくなくない…?


MySQL :: MySQL Router :: 5.2 Fabric Cache Plug-in

MySQL Routerつらくない(Dockerで誰でも試せるMySQL Fabric Cache Plugin編)

$
0
0
この記事は MySQL Fabric&Routerつらくない Advent Calendar 2015の14日目のエントリーです。

前回の記事で無事にMySQL Fabric Cache Pluginでマスター/スレーブの構成をMySQL Fabricに管理させつつ、コネクションはMySQL Routerでハンドルさせることができたので、みなさんそろそろ

「MySQL Router つらくない!」
「MySQL Fabirc つらいけど試してみたい!」

とテンションが上がっているところだと思います。
お願いします、上げてください。

日々の覚書: MySQL Routerつらくない(MySQL Fabricと組み合わせて使ってみる編)


という訳で、このアドベントカレンダーのために用意した一連のDockerイメージ群を紹介します。



さてじゃあ使い方。真っ新なAmazon Linux AMI 2015.09.1のEC2のインスタンスを20GBのEBSで起動したところからなので、他の環境でもほぼ応用が利くはず。
まずはMySQL Fabricサーバー(mysqlfabricのデーモン(いやフォアグラウンドだけど)とバッキングストアのmysqld)を起動。

# yum install docker
# service docker start
# docker run -d --hostname=mysql-fabric --name mysql_fabric_server yoku0825/mysql_fabric_server
..
# docker inspect -f "{{.Config.Hostname}}, {{.NetworkSettings.IPAddress}}" mysql_fabric_server
mysql-fabric, 172.17.0.1

次にMySQL Fabricにすぐ組み込める状態になっているMySQLサーバーを3台起動。


# docker run -d --hostname=mysql-server1 --name mysql_server1 yoku0825/mysql_fabric_aware
..
# docker run -d --hostname=mysql-server2 --name mysql_server2 yoku0825/mysql_fabric_aware
..
# docker run -d --hostname=mysql-server3 --name mysql_server3 yoku0825/mysql_fabric_aware
..
# docker inspect -f "{{.Config.Hostname}}, {{.NetworkSettings.IPAddress}}" $(docker ps | awk '/mysql_fabric_aware/{print $1}')
mysql-server3, 172.17.0.4
mysql-server2, 172.17.0.3
mysql-server1, 172.17.0.2

コマンドラインクライアントとしてのmysqlfabricも`docker run`で起動。MySQL Utilitiesをインストールしなくても試せる! つらくない!
`mysqlfabric group create`でmy_new_fabricというグループを作成。


# docker run --rm yoku0825/mysql_fabric_command --param=protocol.xmlrpc.address=172.17.0.1:32274 group create my_new_fabric
..
Fabric UUID: 5ca1ab1e-a007-feed-f00d-cab3fe13249e
Time-To-Live: 1

uuid finished success result
------------------------------------ -------- ------- ------
69700c6c-bbd2-4371-9940-1c4e1e0c259a 1 1 1

state success when description
----- ------- ------------- -------------------------------------------------------------
3 2 1.45008e+09 Triggered by <mysql.fabric.events.Event object at 0x25af890>.
4 2 1.45008e+09 Executing action (_create_group).
5 2 1.45008e+09 Executed action (_create_group).


MySQL Fabricサーバーにグループが作成できたら、Fabric-awareなMySQL(MySQL Fabricで管理される側のサーバーをそんな風に呼ぶらしい。正しくないかも知れない)を1つずつ3つ追加。


# docker run --rm yoku0825/mysql_fabric_command --param=protocol.xmlrpc.address=172.17.0.1:32274 group lookup_groups
Fabric UUID: 5ca1ab1e-a007-feed-f00d-cab3fe13249e
Time-To-Live: 1

group_id description failure_detector master_uuid
------------- ----------- ---------------- -----------
my_new_fabric None 0 None


# docker run --rm yoku0825/mysql_fabric_command --param=protocol.xmlrpc.address=172.17.0.1:32274 group add my_new_fabric 172.17.0.2
..
# docker run --rm yoku0825/mysql_fabric_command --param=protocol.xmlrpc.address=172.17.0.1:32274 group add my_new_fabric 172.17.0.3
..
# docker run --rm yoku0825/mysql_fabric_command --param=protocol.xmlrpc.address=172.17.0.1:32274 group add my_new_fabric 172.17.0.4
..

# docker run --rm yoku0825/mysql_fabric_command --param=protocol.xmlrpc.address=172.17.0.1:32274 group lookup_servers my_new_fabric
Fabric UUID: 5ca1ab1e-a007-feed-f00d-cab3fe13249e
Time-To-Live: 1

server_uuid address status mode weight
------------------------------------ --------------- --------- --------- ------
14f81155-a23b-11e5-aa7e-0242ac110002 172.17.0.2:3306 SECONDARY READ_ONLY 1.0
15548339-a23b-11e5-aa35-0242ac110003 172.17.0.3:3306 SECONDARY READ_ONLY 1.0
1591026a-a23b-11e5-aa28-0242ac110004 172.17.0.4:3306 SECONDARY READ_ONLY 1.0

登録できたら、1台をマスターに昇格させる。`mysqlfabric group promote`してやると、1台をマスターに、それ以外をそのマスターのスレーブにするように構成してくれるので、


# docker run --rm yoku0825/mysql_fabric_command --param=protocol.xmlrpc.address=172.17.0.1:32274 group promote my_new_fabric
..

docker run --rm yoku0825/mysql_fabric_command --param=protocol.xmlrpc.address=172.17.0.1:32274 group lookup_servers my_new_fabric
Fabric UUID: 5ca1ab1e-a007-feed-f00d-cab3fe13249e
Time-To-Live: 1

server_uuid address status mode weight
------------------------------------ --------------- --------- ---------- ------
14f81155-a23b-11e5-aa7e-0242ac110002 172.17.0.2:3306 SECONDARY READ_ONLY 1.0
15548339-a23b-11e5-aa35-0242ac110003 172.17.0.3:3306 SECONDARY READ_ONLY 1.0
1591026a-a23b-11e5-aa28-0242ac110004 172.17.0.4:3306 PRIMARY READ_WRITE 1.0

はい、これでMySQL Fabric側完成。


# cat mysqlrouter.ini
[fabric_cache:docker_demo]
address = 172.17.0.1
user = admin

[routing:master]
bind_address= 0.0.0.0:13306
mode = read-write
destinations= fabric+cache://docker_demo/group/my_new_fabric

[routing:slave]
bind_address= 0.0.0.0:23306
mode = read-only
destinations= fabric+cache://docker_demo/group/my_new_fabric

mysqlrouter.iniはルーティング部分がDockerイメージに組み込まれてない(docker build時に指定すると固定になっちゃう && パラメーターではなくiniファイルで指定しないといけないぽい)ので、iniファイルを適当に作ってやって
日々の覚書: MySQL Routerつらくない(MySQL Fabricと組み合わせて使ってみる編)


# docker run -d -v /root/mysqlrouter.ini:/tmp/setup/router.ini yoku0825/mysql_router
..

# docker inspect -f "{{.Config.Hostname}}, {{.NetworkSettings.IPAddress}}" mysql-router
mysql-router, 172.17.0.17

あとはMySQL Routerの指定したポート(↑の場合は13306がマスター用のread-writeモード、23306がスレーブ用のread-onlyモード)を叩いてやれば


# mysql -h 172.17.0.17 -P 13306 -u ap

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

# docker run --rm yoku0825/mysql_fabric_command --param=protocol.xmlrpc.address=172.17.0.1:32274 group lookup_servers my_new_fabric
Fabric UUID: 5ca1ab1e-a007-feed-f00d-cab3fe13249e
Time-To-Live: 1

server_uuid address status mode weight
------------------------------------ --------------- --------- ---------- ------
14f81155-a23b-11e5-aa7e-0242ac110002 172.17.0.2:3306 SECONDARY READ_ONLY 1.0
15548339-a23b-11e5-aa35-0242ac110003 172.17.0.3:3306 SECONDARY READ_ONLY 1.0
1591026a-a23b-11e5-aa28-0242ac110004 172.17.0.4:3306 PRIMARY READ_WRITE 1.0

ちゃんと一致してますね! (mysql-server$nとIPアドレスの末尾1バイトが微妙にズレてるので見にくかった。。)

これで誰でもMySQL Routerで遊べる! つらくない!


このへんとかも合わせてご利用ください。 https://github.com/yoku0825/docker_for_mysqlfabric/blob/master/start_3container_fabric.sh

「MySQL 5.8に欲しい機能は?」って中の人がブログ書いてる件を3か月遅れで

$
0
0
Oracle MySQLの中の人、コミュニティーチームの Morgan Tockerのブログに、"MySQL 5.8に欲しい機能は?"とかいう、どストレートな記事がある。このエントリーはこのブログ…に寄せられた主に コメントの紹介。ブログ本文はタイトルがほぼ説明してるので。



記事自体は2015/09/14のものでちょっと古い(MySQL 5.7がRC2になったから、MySQL 5.8のプランを練ってる、みたいな書き方)んだけれど、出揃ったコメントが結構バラエティーに富んでいて、みんな本当に欲しいものが何なのかちょっと気になる。

以下、コメントの意訳。

人気なのは窓関数とWITH句、データディクショナリー (ただしこれは5.8に既にpushされたっぽいのが クローズされたバグレポから伝わってくる)、CHECK制約あたり。IPv6データ型がちょっと意外。

( ´-`).oO(マテビューって言われないあたりがMySQLぽい

個人的にはCHECK制約とちゃんとしたUnicode実装(あるいはutf8mb4_japanese_ci)は是非欲しい。FULL OUTER JOINも欲しいなー。バイナリーログの有効/無効のオンライン化はされそうな雰囲気がある(ソースは一切ないけど、5.7からの流れ的に)
欲を言うなら Perlで他の言語でストアドプロシージャ書きたいし、パラレルクエリーは夢があっていい。

個人的には
* ストレージエンジンをまたいだパーティショニング
  * 今はストレージエンジンもROW_FORMATも同一でないといけない
    * パーティション単位でCOMPRESSION= 'zlib'とかしたい
    * パーティション単位で古いのはMyISAMとかしたい
      * MyISAMオワコン言わない
* プライマリーキーに依存しないパーティショニング
  * サロゲートキーとdateの複合プライマリーキーはもううんざりだ。。
* ALTER TABLEでのmyisampack
  * MyISAMオワコン言わない。。
* もっと賢いコマンドラインクライアント、たとえば mycliみたいな。
* information_schema.innodb_buffer_pageの代わりになるようなperformance_schema
が欲しいです。

サンタさん、いい子にしてますのでよろしくお願いします。


よければみなさんの欲しい機能も教えてください :)

ConoHaで自作Billing Alertを使っているはなし

$
0
0
このエントリーは ConoHa Advent Calendar 2015 の25日目のエントリーです。メリークリスマス!

22日目に既に ConoHaちゃんが好きすぎるので、WebAPIを叩くためのGemを(途中まで)作ってみた という記事がありますが、どう見てもカブっています本当にありがとうございました。
(わたしはConoHaちゃんが好きすぎて作ったわけではないですが)

TL;DR

すぐに落とせば大丈夫だと思ってConoHaの一番高いヤーツでベンチマークを流したら、消すのを忘れて痛い目を見た(巷で噂になっているアレほどではない)

ちなみに 論理削除Casual Talks のときの、「tpcc-mysqlを論理削除に魔改造したらどれくらい速く / 遅くなるか」というものすごくどうでもいいベンチマークに使いました。つらい。

使い方

$ git clone https://github.com/yoku0825/p5-conoha-api.git
$ cd p5-conoha-api
$ cpanm --installdeps .
$ ppit set conoha
---
"password": 'your_api_password'
"tenantId": 'your_tenantId'
"username": 'your_api_username'
$ bin/conoha-billing
775

( ゚д゚) あら簡単

自分用なので色々投げやりです。username, password, tenantIdはConfig::Pitに"conoha"で設定されているとハードコードしてあります。きっと車輪の再発明で、どこにでもいくらでもクライアントは転がってそうなんですが、調べるよりはHTTP::Tinyで書いた方が早いなと思って自分に必要なものだけbinに詰めた感じ。

基本的にTokenさえ取ってdefault_headersに詰めた後はどうにでもなるので、使いたいAPIが出てきてから拡張するのは難しくない、はず。

bin/conoha-billingは単に最新のinvoiceを取ってきて数字だけぬるっと返すので、cronに

$ crontab -l
0 10 * * * [ $(/home/yoku0825/git/p5-conoha-api/bin/conoha-billing) -gt 1000 ] && (echo "Billing alert!!" | mail -s "conoha billing alert" yoku0825@gmail.com)

としておくだけで、簡単に事故は防げるわけです。がんばれ、過去の俺。

yoku0825/p5-conoha-api



ちなみに普段、ConoHaのVPSは
* MroongaのDockerイメージをビルドしたりテストしたり
* rpmパッケージをビルドしたり
* MySQLのソースコード読むのに使ってたり
* Slackに定期的にポストを投げ込んでくれるbotがDockerコンテナーで動いてAPIの口を開けてたり
* GitBucketのDockerコンテナーが動いていたり
* MySQLが4つくらい起動していたり
* MySQL Routerがつらくなかったり
します。

グローバルIP持っててお安いのはいいですね(でもDockerとか色々詰め込んでるから、現プランのSSD 50GBより旧プランの100GBの方が良かったかな。。)

ちなみに3次元のこのはちゃんは初代の人がすきです :)


それでは、良いお年を。

MySQL Routerをlocalhostに置いたらどれくらいの遅延になるかを考えるメモと、MySQL Routerは本当につらくなかったのかのまとめ

$
0
0
この記事は MySQL Fabric&Routerつらくない Advent Calendar 2015 の25日目の記事です。

まずはどうやって遅延を計測しようか考えているメモをだだだと書きなぐる。後半にまとめっぽいものを。

計測環境。サーバーもクライアントもc4.xlarge。サーバーはyumリポジトリーで5.7.10を突っ込んで起動しただけ。


# rpm -i http://dev.mysql.com/get/mysql57-community-release-el6-7.noarch.rpm
# yum install -y mysql-community-server
# service mysqld start
# grep password /var/log/mysqld.log
# mysql -p
mysql> UNINSTALL PLUGIN validate_password;
mysql> CREATE USER yoku0825;

クライアントは接続してSELECT NOW()して切断するだけのもの。


$ cat -n test.cc
1 #include <unistd.h>
2 #include "mysql.h"
3
4 int main()
5 {
6 MYSQL mysql;
7 int m;
8
9 mysql_init(&mysql);
10
11 for (m= 0; m <= 10000; m++)
12 {
13 mysql_real_connect(&mysql, "172.31.15.74", "yoku0825", "", NULL,
14 3306, NULL, 0);
15 mysql_query(&mysql, "SELECT NOW()");
16 MYSQL_RES *res= mysql_store_result(&mysql);
17 mysql_close(&mysql);
18 }
19 }
$ gcc -I/usr/include/mysql -L/usr/lib64/mysql -lmysqlclient -g3 -O0 test.cc
$ time ./a.out

real 0m6.865s
user 0m0.248s
sys 0m0.552s

直接接続だと大体700usくらい。
c4.xlargeは4コアあるので4つ起動してみると


$ time (./a.out & ./a.out & ./a.out & ./a.out ; wait)


real 0m22.573s
user 0m0.508s
sys 1m10.168s

これくらい。ダメだ。ほとんどsysにもっていかれてる。まともに並列処理を書けるC力がほしい。
Routerはソースからコンパイル。サーバーホストだけを指定して単にプロキシーするだけの状態にする。


$ wget http://dev.mysql.com/get/Downloads/MySQL-Router/mysql-router-2.0.2.tar.gz
$ tar xf mysql-router-2.0.2.tar.gz
$ cd mysql-router-2.0.2
$ cmake .
$ make
$ sudo make install
$ sudo cp -ip /usr/local/share/doc/mysqlrouter/sample_mysqlrouter.ini /usr/local/etc/mysqlrouter/mysqlrouter.ini
$ sudo vim /usr/local/etc/mysqlrouter/mysqlrouter.ini
..
[routing:basic_failover]
# To be more transparent, use MySQL Server port 3306
bind_port = 7001
mode = read-write
destinations = 172.31.15.74
..

mysql_real_connectの向き先を127.0.0.1:7001に変えて、中央値は大体これくらい。


$ time ./a.out

real 0m8.816s
user 0m0.192s
sys 0m0.916s

$ time (./a.out & ./a.out & ./a.out & ./a.out ; wait)

real 0m9.761s
user 0m1.432s
sys 0m4.192s

コンテキストスイッチが減ってRouter経由の方が快適という結果に。top見てても遥かに良い感じ(mysqlrouterが100%張り付くので、a.outは20%くらいずつになる。これが結果として綺麗にキャップになっていい感じになるという。。)
もっとちゃんとしたクライアントだと、シングルスレッドのmysqlrouterが問題になってくると思う。取り敢えず、10秒で10000コネクション * 4プロセスだから4000connection/sならさばけてる(mysqlrouterの%usrが張り付くけど)

これを超えてくる場合、mysqlrouterのプロセスを複数起動して分散させることになるし、MySQL Routerはどうやら動的に設定を変えることはできないので、gracefulっぽくするためには



tcpdump + pt-query-digestでレイテンシーを見てみると、直接接続の場合はmysql_real_connectの中央値が467usにクエリーの中央値が152us。

# Query 1: 1.40k QPS, 0.70x concurrency, ID 0x5D51E5F01B88B79E at byte 42297981
# This item is included in the report because it matches --limit.
# Scores: V/M = 0.00
# Time range: 2015-12-24 10:33:50.782145 to 10:33:57.922968
# Attribute pct total min max avg 95% stddev median
# ============ === ======= ======= ======= ======= ======= ======= =======
# Count 33 10000
# Exec time 75 5s 431us 1ms 498us 568us 39us 467us
# Rows affecte 0 0 0 0 0 0 0 0
# Query size 43 292.97k 30 30 30 30 0 30
# Warning coun 0 0 0 0 0 0 0 0
# String:
# Databases
# Hosts 172.31.5.70
# Users yoku0825
# Query_time distribution
# 1us
# 10us
# 100us ################################################################
# 1ms #
# 10ms
# 100ms
# 1s
# 10s+
administrator command: Connect\G

# Query 2: 1.40k QPS, 0.22x concurrency, ID 0xF450CBDB69FA3A64 at byte 42299217
# This item is included in the report because it matches --limit.
# Scores: V/M = 0.00
# Time range: 2015-12-24 10:33:50.782371 to 10:33:57.923132
# Attribute pct total min max avg 95% stddev median
# ============ === ======= ======= ======= ======= ======= ======= =======
# Count 33 10000
# Exec time 24 2s 142us 419us 159us 204us 16us 152us
# Rows affecte 0 0 0 0 0 0 0 0
# Query size 17 117.19k 12 12 12 12 0 12
# Warning coun 0 0 0 0 0 0 0 0
# String:
# Databases
# Hosts 172.31.5.70
# Users yoku0825
# Query_time distribution
# 1us
# 10us
# 100us ################################################################
# 1ms
# 10ms
# 100ms
# 1s
# 10s+
# EXPLAIN /*!50100 PARTITIONS*/
SELECT NOW()\G


Router経由だと固定でレイテンシーがあがるだけだと思ってたけど、Connectの方がぁゃιぃ。フツーのクエリーを計測するよりも(ルーティング判定のぶん)コストが高そう。もうちょっと別のクエリーも投げるパターンにして計測しないとダメだ。

単純クエリーで10usくらいの差なら、まともなクエリーになるに従って十分問題ないと見ていいはず。あ、でも、往復のパケット量が増えればそれぞれのパケットにレイテンシーが載るから結果セットが転送されきるまでのレイテンシーは無視できないのかも知れない。これも計測パターン増やさないと。


# Query 1: 1.07k QPS, 0.62x concurrency, ID 0x5D51E5F01B88B79E at byte 5655092
# This item is included in the report because it matches --limit.
# Scores: V/M = 0.00
# Time range: 2015-12-24 10:35:26.307560 to 10:35:35.641810
# Attribute pct total min max avg 95% stddev median
# ============ === ======= ======= ======= ======= ======= ======= =======
# Count 33 10000
# Exec time 76 6s 488us 1ms 581us 690us 65us 541us
# Rows affecte 0 0 0 0 0 0 0 0
# Query size 43 292.97k 30 30 30 30 0 30
# Warning coun 0 0 0 0 0 0 0 0
# String:
# Databases
# Hosts 172.31.5.70
# Users yoku0825
# Query_time distribution
# 1us
# 10us
# 100us ################################################################
# 1ms #
# 10ms
# 100ms
# 1s
# 10s+
administrator command: Connect\G

# Query 2: 1.07k QPS, 0.19x concurrency, ID 0xF450CBDB69FA3A64 at byte 45635077
# This item is included in the report because it matches --limit.
# Scores: V/M = 0.00
# Time range: 2015-12-24 10:35:26.307968 to 10:35:35.642044
# Attribute pct total min max avg 95% stddev median
# ============ === ======= ======= ======= ======= ======= ======= =======
# Count 33 10000
# Exec time 23 2s 145us 577us 174us 214us 28us 159us
# Rows affecte 0 0 0 0 0 0 0 0
# Query size 17 117.19k 12 12 12 12 0 12
# Warning coun 0 0 0 0 0 0 0 0
# String:
# Databases
# Hosts 172.31.5.70
# Users yoku0825
# Query_time distribution
# 1us
# 10us
# 100us ################################################################
# 1ms
# 10ms
# 100ms
# 1s
# 10s+
# EXPLAIN /*!50100 PARTITIONS*/
SELECT NOW()\G

MySQL Fabricが載ってくると当然結果は変わるだろうから、次はFabric Cache Pluginバージョンも試す(台数がかさむなぁ。。)

ダラダラ書いたけど、次に測らないといけないところはわかった気がする。


さて、本題(?)

MySQL Routerは本当につらくなかったのか?

残念ながらつらくなかったです。


# 情報量(主にドキュメント)

* 初期のMySQL Fabricはひどかった。
* それよりは、今のMySQL Routerはマシ。
* ただし、今のMySQL Fabricの方がMySQL Routerより情報ある(と思う)
* つまり、結局は今後に期待。
  * ただ、MySQL FabricよりはMySQL Routerの方が流行ると思うので、日本語の情報も充実していくんじゃないかなぁ。

# 安定性

* MySQL Fabricも良くなってるんじゃないか疑惑
  * 去年試してた時は結構さっくり刺さってハングしてたけど、今年は1回も突き刺さってない
* MySQL Routerは取り敢えず安定。Fabric Cache Plugin使ってる時の動作が若干不安…?
  * 再現しないので、要追試。

# 使いやすさ、使いたさ

## MySQL Fabric

* グラフィカルなCLIでレプリケーションクラスターの管理。したい。
* GTID依存なので、まずGTIDをONにして回るのが大変。
  * バックアップからリストアしたやつをレプリケーションクラスターに追加とかもGTID依存なので、5.6だと苦労しそう(5.7ならきっといける)
  * GTID依存だからこそ色々簡単なんだよなー、と思うとGTIDはやっぱりいいものだったのだな。
  * MariaDBさんがいるとダメ(GTIDの実装が全然違って互換性がない)
* いろんな実装がコネクター依存すぎてブラックボックスが超怖い。Connector/Jでは上手くいくけど他はダメとかフツーにありそう。

## MySQL Router

* 次世代MySQL Proxyとして使い始められる
  * スレーブの分散、Routerに置き換えようかと真面目に検討中。
  * Lua書かなくていい。ただし、MySQL RouterのプラグインはC++な上にドキュメントがまだないぽい。
* Fabric Cache PluginはMySQL Fabricのブラックボックスを解消するものなので、とてもイケていると思う。
  * ただ、アレだけで1章書いてもいいんじゃないかってくらいもっと説明して欲しいところがいっぱいある。。
* MySQL Fabricと連携させないなら、4.0 5.5とかでもイケるんじゃないかな(old_passwordの壁があるか…?)

# デバッグとソースコードリーディング

* MySQL FabricはPythonで、俺のPython力の欠如によりつらかった
  * ゆるぼ: Pythonista
* MySQL RouterはC++でgdbで突き刺すスタイルなのでデバッグが楽
* とはいえこれはMySQLオタク向けの項目なので、ほとんどの人には無縁

というわけで!

つらくないよ!

つらくないから、僕と結託して 地雷 MySQL Router友達になってよ!

お待ちしております :)

2015年のMySQL 5.7騒動を振り返って

$
0
0
ポエムです。
もう1時間半ちょっとで年も明けますが、今年も色々な方にお世話になりました。ありがとうございました。


2015年は振り返るまでもなくMySQL 5.7漬けでした。
というか、去年はまだMySQL 5.7漬けじゃなかったんですね。そのことにむしろびっくり。もっと長い間 disり調べ続けていたような気がします。

2015/03のPercona Liveに合わせてリリースされた(と思う) MySQL 5.7 最大のリリース、MySQL 5.7.6-m16 に始まり、あっという間にリリースされた MySQL 5.7.7-rc、フィーチャーフリーズされずに出てきた MySQL 5.7.8-rc (RC2) 、それどころかフィーチャーフリーズされないまま、RC2からGAで機能追加がされているという MySQL 5.7.9-GA。。

ちなみにMariaDB 10.0が 10.0.9(RC2)でフィーチャーフリーズせずに新機能を追加した時に「馬鹿なwwww」とか思った記憶があるんですが、見事にMySQLにブーメランになって返ってきましたね。アイタタタタ。

そしてMySQL史上類を見ない膨大な量のリリースノート。htmlタグ込みとはいえ、5.7.6だけじゃなくて他のも十分すごい。


$ curl -s http://dev.mysql.com/doc/relnotes/mysql/5.7/en/news-5-7-1.html | wc
2631 13546 158162

$ curl -s http://dev.mysql.com/doc/relnotes/mysql/5.7/en/news-5-7-2.html | wc
5028 26154 307853

$ curl -s http://dev.mysql.com/doc/relnotes/mysql/5.7/en/news-5-7-3.html | wc
2392 11339 139404

$ curl -s http://dev.mysql.com/doc/relnotes/mysql/5.7/en/news-5-7-4.html | wc
3056 14810 184922

$ curl -s http://dev.mysql.com/doc/relnotes/mysql/5.7/en/news-5-7-5.html | wc
4322 22095 261145

$ curl -s http://dev.mysql.com/doc/relnotes/mysql/5.7/en/news-5-7-6.html | wc
4760 24208 302234

$ curl -s http://dev.mysql.com/doc/relnotes/mysql/5.7/en/news-5-7-7.html | wc
1471 6774 91604

$ curl -s http://dev.mysql.com/doc/relnotes/mysql/5.7/en/news-5-7-8.html | wc
4178 21629 268333

$ curl -s http://dev.mysql.com/doc/relnotes/mysql/5.7/en/news-5-7-9.html | wc
3054 14926 186386

$ curl -s http://dev.mysql.com/doc/relnotes/mysql/5.7/en/news-5-7-10.html | wc
1278 5749 78511

こうしてみると確かに5.7.7は確かにRCだけど5.7.6から1か月くらいで出たし、印象薄いな。。
ともあれ、無事(大きな事故もなく?)GAとしてリリースされたMySQL 5.7、2年越しで(節目節目でとはいえ)更新を追っかけ続けて、結構疲れました。しばらくはこの1年間の貯蓄ででつつましく導入&運用していきたい感じ。

slideshareを見ても、4月MyNAのInnoDB FTSを皮切りにMySQL 5.7を(リライトも再演もやったので、結構な回数になった)積極的にやってきたんだなーって感じになりました。

やってきたんだなーっていうか、やりすぎたなーと思っています(二重の意味で)
"MySQL 5.7"で"罠"がサジェストされるとは。正直すまんかった。

「5.7はn倍速い」
「5.7は新機能がいっぱい」
うん、それは知ってる。でも気を付けなきゃユーザーが痛い目見るものもあるよね? それも紹介してよ。

そんなコンセプトでGAまで活動を続けた結果。


MySQL :: 資料ダウンロード > MySQL 最新情報セミナー2015秋 > MySQL 5.7 新旧パラメタ比較

「どこかの誰かさんがMySQL 5.7のパラメーターは罠い罠い言い続けた結果」
「お客様からも"5.7は罠なの?"とお問い合わせをいただくようになり」
「こんなものを用意した」

(∩´∀`)∩ワーイ

これが俺の今年の一番の成果であることは間違いありません。
これを「○racle公式の資料」として掲載するために、某氏とか某氏とか結構骨を折ってもらったんじゃないかという気もします。ありがとうございました。


あ、この資料、2015/12/31 22:16 JST時点の版だとlog_error_verbosityのデフォルト値が間違ってるので注意してくださいね :-P

それではまた来年もよろしくお願いします。
来年は何してるんだろうなー。

MySQL 5.7.11でdefault_password_lifetimeのデフォルトが0になるらしい!

$
0
0
日々の覚書: MySQL 5.7.4で導入されたdefault_password_lifetimeがじわじわくる の公開以来大反響をいただいていた (ブクマがすごいことになっていて、思わず ばぐれぽにブクマのリンクを貼り付けたほど)default_password_lifetimeが。

5.7.11でついにデフォルト0になる!!! やった!!! やったよ!!!

ドキュメントはもう"default: 0 (>= 5.7.11)"の記載になってる。


これで、

MySQL 5.7にアップデートしてから

360日後にやってくる

_人人人人人人_
> 突然の死 <
 ̄Y^Y^Y^Y^Y^Y^ ̄

はなくなりました! (∩´∀`)∩ワーイ


いいぞ○racle!
"Affects me"してくれたみなさんもありがとうございました。


( ´-`).oO(さて、また自分が罠にならないように、あちこちのもの直さないとなー


【2015/01/14 13:59】
MySQL Bugs: #77277: default_password_lifetime should be set 0 as implicit default value

の最後の方で、Morgan Tocker(Oracle MySQL Community Manager改めOracle MySQL Product Manager)が他にも色々(default_password_filetime > 0だったらワーニング出そうぜとか SYSスキーマでいつexpireされるか見るビュー作ろうぜとか)出してくれてる。



@morgo++

MySQL Bug #79977 "utf8mb4_unicode_520_ci don't make sense for Japanese FTS"で言いたいこと

$
0
0
英語で書いてたら自分でもよくわからなくなってきたので。

MySQL Bugs: #79977: utf8mb4_unicode_520_ci don't make sense for Japanese FTS


* 本質的には MySQL Bugs: #76553: Sushi-Beer issue of MySQL with utf8mb4 の関連。
  * Unicode実装が不完全だから起こっている問題。


* 丸め問題

  * motherを意味する書き方に、はは(ひらがな)とハハ(カタカナ)とハハ(半角カタカナ)があって、
  * これらは(文字の形は違うけど)同じ音で同じ意味だから、大概の場合同じ文字として扱ってくれると嬉しい。


* 病院美容院問題

  * hospitalを意味する"びょういん"とheir salonを意味する"びよういん"
  * "ょ"と"よ"は文字の形は似てるけど、ほとんどの場合これは違う意味を持つから区別しないといけない。


* ハハパパ問題

  * Bug #76553でも説明されてるけど、ハハ(濁音なし)はmother、ババ(濁点)は"grand mother"、パパ(半濁点)は"daddy"を意味する。
  * これらは区別されないと困る。超困る。


* 最後に半角全角問題

  * "MySQL"と"MySQL"は同じに扱いたいよね。

* Hiragana-Katakanaと半角全角を区別しちゃうのは、不便だけど我慢できる。
* でも拗音促音と濁点半濁点を区別 *されない* のは(特に全文検索の側面で)機能要件を満たせない。LIKE演算子でもそうだけど。
  * 「びょういん」って検索して美容院が結果に含まれたら変でしょ?
* あと、これ、UNIQUE制約をぶっ壊す可能性があるのよね。
  * UNIQUE KEY(家族関係)って制約があって、俺には母親がいるからハハって入れて、俺に父親がいるからパパって入れようとすると、エラるよね? (いわゆる kamipoさんのハハパパ問題


表にまとめるとこんな感じ。ホントはutf8mb4_japanese_ciが欲しいところだよねって。

|                    | utf8mb4_bin | utf8mb4_general_ci | utf8mb4_unicode_ci | utf8mb4_unicode_520_ci|
|--------------------|-------------|--------------------|--------------------|-----------------------|
| Hiragana-Katakana  | cs (unkind) | cs (unkind)        | ci (good)          | ci(good)              |
| Youon              | cs (good)   | cs (good)          | ci (critical)      | ci(critical)          |
| Dakuten-Handakuten | cs (good)   | cs (good)          | ci (critical)      | ci(critical)          |
| Wide-Narrow        | cs (unkind) | cs (unkind)        | ci (good)          | ci(good)              |
| Sushi-Beer         | cs          | ci                 | ci                 | cs                    |

AnemometerというMySQLスローログ専用の可視化ツールの弱点と、その克服スクリプト

$
0
0
一部の人にしか知られていない AnemometerというMySQLのスローログ専用の可視化ツールがある。

box/Anemometer: Box SQL Slow Query Monitor


中身はpt-query-digestの テーブル出力機能(サマライズした結果をMySQLのテーブルに保存する機能があるのだ)に依存していて、スローログの可視化というよりはpt-query-digestの可視化というのがたぶん正しい。

だけどこのやり方にはちょっと弱点があって、pt-query-digestはクエリーをサマライズする時に発生時間の情報を 「そのダイジェストが最初に現れた時間(ts_min)」と「そのダイジェストが最後に現れた時間(tx_max)」 という値にサマライズしてしまう。よく見る出力結果の中では"Time range"として表示されている。


# Query 14: 0.00 QPS, 0.00x concurrency, ID 0xAF17C328E1020443 at byte 10391443
# This item is included in the report because it matches --outliers.
# Scores: V/M = 13.70
# Time range: 2015-11-02 10:55:08 to 2015-12-18 15:20:14
# Attribute pct total min max avg 95% stddev median
# ============ === ======= ======= ======= ======= ======= ======= =======
# Count 0 13
# Exec time 0 163s 2s 51s 13s 29s 13s 9s
# Lock time 0 5ms 271us 445us 354us 424us 65us 301us
# Rows sent 9 544.87k 3.91k 104.73k 41.91k 97.04k 33.62k 46.68k
# Rows examine 0 12.00M 238.87k 3.04M 945.00k 1.46M 808.81k 725.01k
# Query size 0 24.29k 1.87k 1.87k 1.87k 1.86k 0.00 1.86k

コマンドラインからの出力結果を眺める分には便利なんだけど、グラフ化しようという時にこれはつらい。Anemometerはts_min(最初にスローログに現れた時刻)をグラフにプロットするので、今既にあるスローログをpt-query-digestで食わせてAnemometerで表示させると、チェックサムごとにts_minの時刻に1回だけスパイクしたようなグラフになってしまう。

これは、上手くない(´・ω・`)






pt-query-digest側でこの集約を無効化できればいいんだけど、オプションとしては存在せず、中に手を入れるにしても結構奥まったところにあっていじくりたくない。

定期的にpt-query-digestに--since, --until オプションを使って食わせてもいいんだけど、秒単位とは言わずとも分単位くらいでは見たい…となると、1日ぶんのログファイルを食わせるのに見るかどうかもわからないのに1440回pt-query-digestを起動しなければならない。それも嫌だ。


という訳で乱雑に書いたPerlスクリプト。Anemometer用にスローログを食ってくれるのでanemoeater(どや
my_script/anemoeater.pl at master · yoku0825/my_script


$ ./anemoeater.pl --docker path_to_slowlog

シンプル。--dockerを使わなくとも、Anemometerが既に構成されている環境があれば、--hostとか--userで指定してやればいい。--dockerに任せると構成済みの yoku0825/anemometerを起動して、スクリプトで1分ごとに丸めて送る。

パイプでpt-query-digestを呼ぶので結構遅い(11Mのスローログを食わせるのに、pt-query-digestで直接食わせると10秒ちょい、anemoeaterだと24パラにしても1分ちょい)

これで無事、過去のスローログをまとめて食わせても




ちゃんとジグザグしてくれるようになった。

2015/12/01 00:00~2016/01/01 23:59のログを8並列でぶち込むのはこんな感じで書きます。

$ ./anemoeater.pl --since 201512010000 --until 201601012359 --parallel 8 --docker path_to_slowlog
real 0m17.471s
user 1m47.847s
sys 0m11.117s

これでDockerでAnemometerの起動まで終わってるんだから、都度都度パースして突っ込めそう。しめしめ。

MySQL 5.7.8から導入されたVersion Tokenとやらの動作

$
0
0
ドキュメントはこちら。
MySQL :: MySQL 5.7 Reference Manual :: 5.1.8.4 Version Tokens

サーバーの持ってるトークンとクライアントが持ってるトークンを比較して、一致しなければエラーにしてくれる仕組み。


取り敢えず何はなくともインストール。version_token.soはバンドルされてるので、ドキュメントの通りにINSTALL PLUGINとCREATE FUNCTIONを貼り付ければOK。


mysql> INSTALL PLUGIN version_tokens SONAME 'version_token.so';
Query OK, 0 rows affected (0.00 sec)

mysql> CREATE FUNCTION version_tokens_set RETURNS STRING SONAME 'version_token.so';
Query OK, 0 rows affected (0.00 sec)

mysql> CREATE FUNCTION version_tokens_show RETURNS STRING SONAME 'version_token.so';
Query OK, 0 rows affected (0.00 sec)

mysql> CREATE FUNCTION version_tokens_edit RETURNS STRING SONAME 'version_token.so';
Query OK, 0 rows affected (0.00 sec)

mysql> CREATE FUNCTION version_tokens_delete RETURNS STRING SONAME 'version_token.so';
Query OK, 0 rows affected (0.00 sec)

mysql> CREATE FUNCTION version_tokens_lock_shared RETURNS INT SONAME 'version_token.so';
Query OK, 0 rows affected (0.00 sec)

mysql> CREATE FUNCTION version_tokens_lock_exclusive RETURNS INT SONAME 'version_token.so';
Query OK, 0 rows affected (0.00 sec)

mysql> CREATE FUNCTION version_tokens_unlock RETURNS INT SONAME 'version_token.so';
Query OK, 0 rows affected (0.00 sec)

mysql> SHOW GLOBAL VARIABLES LIKE 'version_token_%';
+-------------------------------+-------+
| Variable_name | Value |
+-------------------------------+-------+
| version_tokens_session | |
| version_tokens_session_number | 0 |
+-------------------------------+-------+
2 rows in set (0.00 sec)


サーバー側のトークンを設定するにはUDFを使うので、剥き出しで使うにはSELECTステートメントを使う。サーバー側のトークンを設定したり読み出したりするにはSuper権限が必要。

mysql> SELECT version_tokens_set('MySQL=dolphin');
+-------------------------------------+
| version_tokens_set('MySQL=dolphin') |
+-------------------------------------+
| 1 version tokens set. |
+-------------------------------------+
1 row in set (0.00 sec)

mysql> SELECT version_tokens_show();
+-----------------------+
| version_tokens_show() |
+-----------------------+
| MySQL=dolphin; |
+-----------------------+
1 row in set (0.00 sec)


クライアント側のトークンはSETステートメントで指定する…せめてConnector/Cにはmysql_optionsとか無いの?


mysql> SELECT CURRENT_USER();
+----------------+
| CURRENT_USER() |
+----------------+
| yoku0825@% |
+----------------+
1 row in set (0.00 sec)

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

mysql> SET version_tokens_session= 'MySQL=sealion!?';
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT CURRENT_USER();
ERROR 3136 (42000): Version token mismatch for MySQL. Correct value dolphin

mysql> SELECT @@version_tokens_session;
ERROR 3136 (42000): Version token mismatch for MySQL. Correct value dolphin

*クライアント側のバージョントークンが指定されていない場合はバージョントークンの比較はされない*
クライアント側でバージョントークンが指定されていて、かつ、サーバーのバージョントークンと違う場合はError: 3136が返される。
権限とかそういうレベルではなく、ステートメントそのものが拒否される状態。


mysql> SET version_tokens_session= 'MySQL=dolphin';
ERROR 3136 (42000): Version token mismatch for MySQL. Correct value dolphin

バージョントークンを設定し直そうとしてもバージョントークンのチェックに引っかかってSETステートメントが転ける。おとなしく繋ぎ直す他にない。


mysql> SET version_tokens_session= 'MariaDB=sealion';
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT CURRENT_USER();
ERROR 3137 (42000): Version token MariaDB not found.

存在しないトークンをセットした場合もステートメントが転ける。


最初にチラ見した時に「これ使えば追加要素認証(ユーザー名、パスワードの他にトークンを使う)もできる?」とか思ったけど、クライアント側のトークンをセットしない場合は比較されないし、期待されている値をエラーメッセージに埋め込んでしまうのでそういう用途には使えない。


mysql> SELECT version_tokens_set('MariaDB=sealion');
+---------------------------------------+
| version_tokens_set('MariaDB=sealion') |
+---------------------------------------+
| 1 version tokens set. |
+---------------------------------------+
1 row in set (0.00 sec)

mysql> SELECT version_tokens_show();
+-----------------------+
| version_tokens_show() |
+-----------------------+
| MariaDB=sealion; |
+-----------------------+
1 row in set (0.00 sec)

mysql> SELECT version_tokens_edit('MySQL=dolphin');
+--------------------------------------+
| version_tokens_edit('MySQL=dolphin') |
+--------------------------------------+
| 1 version tokens updated. |
+--------------------------------------+
1 row in set (0.00 sec)

mysql> SELECT version_tokens_show();
+--------------------------------+
| version_tokens_show() |
+--------------------------------+
| MariaDB=sealion;MySQL=dolphin; |
+--------------------------------+
1 row in set (0.00 sec)

version_tokens_setだとトークンを追加はできなくて上書かれる。セミコロン区切りで'MySQL=dolphin;MariaDB=sealion'と渡してやるか、version_tokens_editで指定する(editといいつつINSERT .. ON DUPLICATE UPDATEと同じような動き)


mysql> SET version_tokens_session= 'MariaDB=sealion';
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT CURRENT_USER();
+----------------+
| CURRENT_USER() |
+----------------+
| yoku0825@% |
+----------------+
1 row in set (0.00 sec)

mysql> SET version_tokens_session= 'MariaDB=sealion;sushi=beer';
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT CURRENT_USER();
ERROR 3137 (42000): Version token sushi not found.

サーバー側に複数のトークンがセットされている場合、クライアントが申告したトークンが全てサーバーに含まれていればステートメントは成功する。要らないものを付けると失敗する。

ドキュメントの例を見る限り、read_onlyを指定できないマルチマスターな環境下でそれでも特定の粒度でマスターを分ける、みたいな感じに使うことを想定されているんだろうか。Group Replicationですかそうですかわかりますん。

ぱっと思い付いたのは、WEBサービスのAPIをバージョンごとに公開している場合なんかにAPIのコードにSET SESSION version_tokens_session= 'v1=true'みたいなのを入れておいて、v2が作られたらサーバー側のトークンを'v1=true;v2=true'にして、v1が消し去られるタイミングでversion_tokens_edit('v1=false')ってやってやると、API v1から来るクエリーだけを転けさせられるようになる、みたいな感じだろうか。

ただ、サーバー側のトークンは揮発性(MySQLの再起動で消える)ので、ちょっと微妙な感じはする。どう使えるだろう。


あとこのプラグインの面白いところは、これAudit PluginなのでAudit Pluginの書き方のサンプルとして面白かった。 このへん。

WHERE .. IN (..)のリストの順番でソートするORDER BY FIELDの仕組み

$
0
0
MySQLには`WHERE col IN (..) ORDER BY FILED(col, ..)`という書き方でINに並べた順番にソートしなおせるという知見がある。


こんなテーブルがあって、

mysql56> SELECT * FROM t1;
+-----+-------+
| num | val |
+-----+-------+
| 1 | one |
| 2 | two |
| 3 | three |
| 4 | four |
| 5 | five |
| 6 | six |
| 7 | seven |
| 8 | eight |
| 9 | nine |
| 10 | ten |
+-----+-------+
10 rows in set (0.00 sec)


INにテキトーな値を並べてやっても、

mysql56> SELECT * FROM t1 WHERE num IN (7, 5, 3);
+-----+-------+
| num | val |
+-----+-------+
| 3 | three |
| 5 | five |
| 7 | seven |
+-----+-------+
3 rows in set (0.00 sec)


IN演算子に渡した順番には返ってこない、これがフツーの動作。

これを7 => 5 => 3の順番で返してほしいとか 業の深い俺の知らないところで大変な何かを抱えている人もいたりするので、そんな時に使うのがORDER BY FIELD()

mysql56> SELECT * FROM t1 WHERE num in (7, 5, 3) ORDER BY FIELD(num, 7, 5, 3);
+-----+-------+
| num | val |
+-----+-------+
| 7 | seven |
| 5 | five |
| 3 | three |
+-----+-------+
3 rows in set (0.00 sec)


初めて見た時はファッ!? ってなったけど、クエリーをこう書き換えると、たぶんやってることが伝わる。

mysql56> SELECT *, FIELD(num, 7, 5, 3) AS sort_rank FROM t1 WHERE num in (7, 5, 3) ORDER BY sort_rank;
+-----+-------+-----------+
| num | val | sort_rank |
+-----+-------+-----------+
| 7 | seven | 1 |
| 5 | five | 2 |
| 3 | three | 3 |
+-----+-------+-----------+
3 rows in set (0.00 sec)


ORDER BY FIELDはORDER BY句のバリエーションじゃなくて、FILED関数の結果でORDER BYしている。

FIELD関数のドキュメントはこちら。第1引数に検索したい値、第2引数以降に検索元となるリストを与える感じ。
これが、numの値が(7, 5, 3)の何番目にあるかを整数で返すので、そこでソートできる。

http://dev.mysql.com/doc/refman/5.6/ja/string-functions.html#function_field

で、FIELD関数は関数で、ORDER BY FIELDという構文じゃない。
つまり、やってることは「関数の演算結果でソート」だから、ORDER BY狙いのキーは動かない。

mysql56> explain SELECT * FROM t1 WHERE num in (7, 5, 3) ORDER BY FIELD(num, 7, 5, 3);
+----+-------------+-------+-------+---------------+------+---------+------+------+-----------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+------+---------+------+------+-----------------------------+
| 1 | SIMPLE | t1 | range | num | num | 8 | NULL | 3 | Using where; Using filesort |
+----+-------------+-------+-------+---------------+------+---------+------+------+-----------------------------+
1 row in set (0.00 sec)


基本的にWHERE .. IN ..でPRIMARY KEYをリテラルリストで数行~数十行取ってくる時くらいしか安全な使い道はなさそうな気がする(けど、そういうケースでは割と便利だったりする)
ご利用は計画的に。

ORDER BY RAND()はしたくないけどそれなりにランダムな結果セットを返すいくつかの方法を考える

$
0
0
ORDER BY RAND()といえば、「結果セットをランダムにソートし、LIMITと組み合わせることでランダムに指定件数をピックアップしたかのように見える」黒魔術。

( ´-`).oO(そういえばこれも ORDER BY FIELDと一緒で構文だと思っていた人がいたな。。


これもまあRAND()関数を使ってるだけなので、select_listに放り込めば何やってるかわかりやすい。

mysql56> SELECT num, val, RAND() AS rand_val FROM t1 ORDER BY rand_val LIMIT 3;
+-------+----------------------------------+-------------------------+
| num | val | rand_val |
+-------+----------------------------------+-------------------------+
| 94164 | e8d2546088e6be7ff164964c7a07bdb3 | 0.000012977980089353379 |
| 4354 | 46d0671dd4117ea366031f87f3aa0093 | 0.00001926440747386255 |
| 11573 | 2d6304a207cd9469f776e651e81ed7f8 | 0.000023321248612665803 |
+-------+----------------------------------+-------------------------+
3 rows in set (0.09 sec)


余談だけど、RAND()関数は引数にシード値を取れる(引数を取らない場合はテキトーにシード値が設定される)ので、ランダムっぽいけど再現可能な並び順を作り出すこともできる。

mysql56> SELECT num, val, RAND(10) AS rand_val FROM t1 ORDER BY rand_val LIMIT 3;
+-------+----------------------------------+-------------------------+
| num | val | rand_val |
+-------+----------------------------------+-------------------------+
| 78811 | 77cd08791011fb678c97302a06de9999 | 0.000002980232241545089 |
| 48089 | 7a045a3247aa6fafd68634aa6acb941f | 0.000006978400058092922 |
| 34076 | 6dcfff2b73388f6307994658463a9341 | 0.00004350300882337895 |
+-------+----------------------------------+-------------------------+
3 rows in set (0.08 sec)

mysql56> SELECT num, val, RAND(10) AS rand_val FROM t1 ORDER BY rand_val LIMIT 3;
+-------+----------------------------------+-------------------------+
| num | val | rand_val |
+-------+----------------------------------+-------------------------+
| 78811 | 77cd08791011fb678c97302a06de9999 | 0.000002980232241545089 |
| 48089 | 7a045a3247aa6fafd68634aa6acb941f | 0.000006978400058092922 |
| 34076 | 6dcfff2b73388f6307994658463a9341 | 0.00004350300882337895 |
+-------+----------------------------------+-------------------------+
3 rows in set (0.06 sec)

なおこの場合、「そのシードで何番目に生成された乱数か」がキモになるので、WHERE句評価後 *何番目に* その行がフェッチされるかでrand_valの値は変わる。WHERE句を評価した後に、取り出したレコードの順番にRAND()を計算してソートするからだ。


mysql56> SELECT num, val, RAND(10) AS rand_val FROM t1 WHERE num > 30000 ORDER BY rand_val LIMIT 3;
+-------+----------------------------------+-------------------------+
| num | val | rand_val |
+-------+----------------------------------+-------------------------+
| 78089 | 67ebeaa4f6391a89d2b629860fff2c9d | 0.000006978400058092922 |
| 64076 | 6a3f8e5443504151a7306f2a13fae303 | 0.00004350300882337895 |
| 30140 | 8befb4efe8ce6cdf0e1a84974d452a9f | 0.00004702340815870409 |
+-------+----------------------------------+-------------------------+
3 rows in set (0.06 sec)

mysql56> SELECT num, val, RAND(10) AS rand_val FROM t1 WHERE num < 80000 ORDER BY rand_val LIMIT 3;
+-------+----------------------------------+-------------------------+
| num | val | rand_val |
+-------+----------------------------------+-------------------------+
| 78811 | 77cd08791011fb678c97302a06de9999 | 0.000002980232241545089 |
| 48089 | 7a045a3247aa6fafd68634aa6acb941f | 0.000006978400058092922 |
| 34076 | 6dcfff2b73388f6307994658463a9341 | 0.00004350300882337895 |
+-------+----------------------------------+-------------------------+
3 rows in set (0.10 sec)


だから本当に再現可能なランダムっぽい何かをしたい場合、(あるなら)サロゲートキーを渡してゴニョる方がいい気がする。これなら常に「そのシードで1番目の乱数」を使うことになるので、「結果セットの先頭から何番目にあるか」が変わってもRAND()関数の戻す値は変わらない(はず) 並び順をズラす場合、サロゲートキーに何かしら足してやったりかけてやったりすればいい。

mysql56> SELECT num, val, RAND(num) AS rand_val FROM t1 ORDER BY rand_val LIMIT 3;
+-------+----------------------------------+-------------------------+
| num | val | rand_val |
+-------+----------------------------------+-------------------------+
| 15536 | dbe1a0a2c9bd9241b3499318bf96f756 | 0.000004868954424624289 |
| 51034 | 861c3ad6224d443664f925552d2255a1 | 0.00001504737885207625 |
| 86532 | 4169f89a1c9c1e6eaf14b1b1e50967fb | 0.000025210902118320486 |
+-------+----------------------------------+-------------------------+
3 rows in set (0.06 sec)

mysql56> SELECT num, val, RAND(num) AS rand_val FROM t1 WHERE num < 90000 ORDER BY rand_val LIMIT 3;
+-------+----------------------------------+-------------------------+
| num | val | rand_val |
+-------+----------------------------------+-------------------------+
| 15536 | dbe1a0a2c9bd9241b3499318bf96f756 | 0.000004868954424624289 |
| 51034 | 861c3ad6224d443664f925552d2255a1 | 0.00001504737885207625 |
| 86532 | 4169f89a1c9c1e6eaf14b1b1e50967fb | 0.000025210902118320486 |
+-------+----------------------------------+-------------------------+
3 rows in set (0.06 sec)

mysql56> SELECT num, val, RAND(num) AS rand_val FROM t1 WHERE num > 10000 ORDER BY rand_val LIMIT 3;
+-------+----------------------------------+-------------------------+
| num | val | rand_val |
+-------+----------------------------------+-------------------------+
| 15536 | dbe1a0a2c9bd9241b3499318bf96f756 | 0.000004868954424624289 |
| 51034 | 861c3ad6224d443664f925552d2255a1 | 0.00001504737885207625 |
| 86532 | 4169f89a1c9c1e6eaf14b1b1e50967fb | 0.000025210902118320486 |
+-------+----------------------------------+-------------------------+
3 rows in set (0.08 sec)

mysql56> SELECT num, val, RAND(num * 2) AS rand_val FROM t1 WHERE num > 10000 ORDER BY rand_val LIMIT 3;
+-------+----------------------------------+-------------------------+
| num | val | rand_val |
+-------+----------------------------------+-------------------------+
| 25517 | 362f278d9150aaf7894f586b5682de06 | 0.00001504737885207625 |
| 43266 | faeddbfcef4331221e71ed4186e0c65b | 0.000025210902118320486 |
| 61015 | 67f68835939b9fa291a4e417312b4ec1 | 0.00003538932654577245 |
+-------+----------------------------------+-------------------------+
3 rows in set (0.06 sec)


さて本題。
ORDER BY RAND()はWHERE句でフィルターされた後の行全てに対してRAND()関数を適用し、その結果でソートするので、WHERE句でフィルターした後の行が多ければ多いほど重くなるし、WHERE句で十分フィルターが聞いてもUsing temporaryに落ちる。

昔からよく言われることではあるが、アプリケーション側で乱数を作ってWHERE句に指定するのはよくあるやり方だ。その場合、ORDER BY FIELD() はやっぱり役に立つかもしれない。
アプリケーション側と言いながらSQLでやってるのは気にしない。

mysql56> SELECT MAX(num) FROM t1 INTO @max_num;
Query OK, 1 row affected (0.01 sec)

mysql56> SET @r1 := CAST(@max_num * RAND() + 1 AS signed), @r2 := CAST(@max_num * RAND() + 1 AS signed), @r3 := CAST(@max_num * RAND() + 1 AS signed);
Query OK, 0 rows affected (0.00 sec)

mysql56> SELECT num, val FROM t1 WHERE num IN (@r1, @r2, @r3) ORDER BY FIELD(num, @r1, @r2, @r3);
+-------+----------------------------------+
| num | val |
+-------+----------------------------------+
| 68966 | 3ccf50b9d73ea11f758bd030e5ac593f |
| 51533 | 2f6a0826437abc6688b22dfd89d783c0 |
| 50767 | 8deda01d4df57c1d3cb6b1e4a0391fbe |
+-------+----------------------------------+
3 rows in set (0.01 sec)


実際問題、auto_increment(の値とは限らないけれど)の値が一貫して抜けがないことは期待できない(innodb_autoinc_lock_mode= 0だってDELETEすれば抜ける訳で、= 1ならトランザクションをロールバックしたりDuplicate Key Errorを起こすだけでautoincは進む)ので、こんな風に書くしかなかろうか。

mysql56> SELECT MAX(num) FROM t1 INTO @max_num;
Query OK, 1 row affected (0.00 sec)

mysql56> SET @r := @max_num * RAND() + 1;
Query OK, 0 rows affected (0.00 sec)

mysql56> SELECT num, val FROM t1 WHERE num > @r LIMIT 1;
+------+----------------------------------+
| num | val |
+------+----------------------------------+
| 8564 | 621eb0b827c09dd1804e87bd74f79383 |
+------+----------------------------------+
1 row in set (0.00 sec)

mysql56> SET @r := @max_num * RAND() + 1;
Query OK, 0 rows affected (0.00 sec)

mysql56> SELECT num, val FROM t1 WHERE num > @r LIMIT 1;
+-------+----------------------------------+
| num | val |
+-------+----------------------------------+
| 67575 | fd85263468f2e1315a31116cf7b12a00 |
+-------+----------------------------------+
1 row in set (0.00 sec)

mysql56> SET @r := @max_num * RAND() + 1;
Query OK, 0 rows affected (0.00 sec)

mysql56> SELECT num, val FROM t1 WHERE num > @r LIMIT 1;
+-------+----------------------------------+
| num | val |
+-------+----------------------------------+
| 12181 | b75867b590e9a1a38ceaea9f8cb9cf45 |
+-------+----------------------------------+
1 row in set (0.00 sec)

INで仕留めた場合でもLIMIT 1で仕留めた場合でも、SQLのレイヤーでは重複(たとえばnum = 1が2回引っかかる)や空振り(その条件にマッチするレコードが1件もない)を検出しないので、それが嫌ならアプリケーション側で重複判定をする必要がある。それに、必要な件数が揃うまで複数回クエリーを投げる必要があるので、それが嫌な感じはもちろんする(とはいえ、速度的には大概の場合お釣りがくる)。あとはサロゲートキーが極端に偏ってるとこれは死ぬかも知れない。1, 2, 3, .., 1000の次が10万だったりすると、max_num * rand()を超えるレコードはかなりの高確率でnum= 10万だ。


もう一つ、WHERE RAND()という手法もある。`tail -f .. | perl -nle 'if (rand() < 0.01) {print}'`って話を聞いて思い付いただけだけれども。

mysql56> SELECT num, val FROM t1 WHERE rand() < 1/100 ORDER BY num DESC LIMIT 3;
+-------+----------------------------------+
| num | val |
+-------+----------------------------------+
| 99966 | 44d3377fd88bc32cd46acd38d716abd3 |
| 99899 | a9cfebcdb4e20ed975e82b7fd877693f |
| 99790 | 7aab6ba599620439ed28d3cee272c3af |
+-------+----------------------------------+
3 rows in set (0.00 sec)

mysql56> SELECT num, val FROM t1 WHERE rand() < 1/100 ORDER BY num DESC LIMIT 3;
+-------+----------------------------------+
| num | val |
+-------+----------------------------------+
| 99954 | 758f0bc5e561543556e9f4e0d23335cc |
| 99663 | 10ef53cc7b761466d851d05dda6b82e3 |
| 99650 | 02aecc1719dc308a7efac6064861cf93 |
+-------+----------------------------------+
3 rows in set (0.00 sec)


DESCにしてるのは趣味なのでASCでもいいかも知れない(けど、古いものからやるよりは新しいものから引いた方がバッファプール効率がいい) もうちょっと分散させたい場合はRAND()と比較する定数の値を小さくすれば良くて、1/100でLIMIT 3なら、期待値として直近300行をhandler_read_prevして、その中から3件が選ばれる。飽くまで1クエリーで1つのテーブルから取ってくるので、重複はない。が、確率で取ってくることになるのであまりタイトなサンプリングをすると空振り(LIMITで指定した件数が集まらない)はあり得る。あと、この形はサロゲートキーに依存せずインデックスさえあれば好きなインデックスでORDER BYできる。

mysql56> SHOW SESSION STATUS LIKE 'handler_read%';
+-----------------------+-------+
| Variable_name | Value |
+-----------------------+-------+
| Handler_read_first | 0 |
| Handler_read_key | 1 |
| Handler_read_last | 1 |
| Handler_read_next | 0 |
| Handler_read_prev | 312 |
| Handler_read_rnd | 0 |
| Handler_read_rnd_next | 0 |
+-----------------------+-------+
7 rows in set (0.00 sec)


ほぼ期待値通り。 OORDEER BY RAND()とWHEREで3回引くケースはこんな感じだった。

mysql56> SHOW SESSION STATUS LIKE 'handler_read%';
+-----------------------+--------+
| Variable_name | Value |
+-----------------------+--------+
| Handler_read_first | 1 |
| Handler_read_key | 1 |
| Handler_read_last | 0 |
| Handler_read_next | 0 |
| Handler_read_prev | 0 |
| Handler_read_rnd | 3 |
| Handler_read_rnd_next | 200002 |
+-----------------------+--------+
7 rows in set (0.00 sec)

mysql56> SHOW SESSION STATUS LIKE 'handler_read%';
+-----------------------+-------+
| Variable_name | Value |
+-----------------------+-------+
| Handler_read_first | 0 |
| Handler_read_key | 4 |
| Handler_read_last | 1 |
| Handler_read_next | 0 |
| Handler_read_prev | 0 |
| Handler_read_rnd | 0 |
| Handler_read_rnd_next | 0 |
+-----------------------+-------+
7 rows in set (0.00 sec)


直近のPRIMARY KEYはバッファプールに載ってる(INSERTされたままバッファプールに残ってる)率が高い気がするので、偏りが許せるなら実用的だと思う(し、本当に完全ランダムでなきゃいけないならRAND()関数とか使っちゃいけない気がする) これだけ件数が絞れてれば、FROM句に閉じ込めてORDER BY RAND()しても許せなくはないくらいのパフォーマンスのはず。パッと見、numでORDER BYされてるとは思えないような感じに仕上がる(かもしれない)

mysql56> SELECT * FROM (SELECT num, val FROM t1 WHERE rand() < 1/3000 ORDER BY num DESC LIMIT 3) AS dummy ORDER BY RAND();
+-------+----------------------------------+
| num | val |
+-------+----------------------------------+
| 96640 | 3076ef0ad4d1e7c6dec15fb4541b6997 |
| 95735 | 3b2016665210c18767dfe611b76ffbea |
| 97248 | 32d32773f19f2f421ebc2d41da4ef5a9 |
+-------+----------------------------------+
3 rows in set (0.00 sec)

mysql56> show profile cpu;
+--------------------------------+----------+----------+------------+
| Status | Duration | CPU_user | CPU_system |
+--------------------------------+----------+----------+------------+
| starting | 0.000046 | 0.000000 | 0.000000 |
| Waiting for query cache lock | 0.000005 | 0.000000 | 0.000000 |
| init | 0.000008 | 0.000000 | 0.000000 |
| checking query cache for query | 0.000348 | 0.000000 | 0.000000 |
| checking permissions | 0.000016 | 0.000000 | 0.000000 |
| Opening tables | 0.000195 | 0.000000 | 0.000000 |
| init | 0.000019 | 0.000000 | 0.000000 |
| System lock | 0.000018 | 0.000000 | 0.000000 |
| optimizing | 0.000004 | 0.000000 | 0.000000 |
| optimizing | 0.000019 | 0.000000 | 0.000000 |
| statistics | 0.000035 | 0.000000 | 0.000000 |
| preparing | 0.000019 | 0.000000 | 0.000000 |
| Sorting result | 0.000003 | 0.000000 | 0.000000 |
| statistics | 0.000003 | 0.000000 | 0.000000 |
| preparing | 0.000006 | 0.000000 | 0.000000 |
| Creating tmp table | 0.000022 | 0.000000 | 0.000000 |
| Sorting result | 0.000004 | 0.000000 | 0.000000 |
| executing | 0.000014 | 0.000000 | 0.000000 |
| Sending data | 0.000012 | 0.000000 | 0.000000 |
| executing | 0.000002 | 0.000000 | 0.000000 |
| Sending data | 0.001889 | 0.001999 | 0.000000 |
| Creating sort index | 0.000045 | 0.000000 | 0.000000 |
| end | 0.000002 | 0.000000 | 0.000000 |
| removing tmp table | 0.000025 | 0.000000 | 0.000000 |
| end | 0.000009 | 0.000000 | 0.000000 |
| query end | 0.000007 | 0.000000 | 0.000000 |
| closing tables | 0.000002 | 0.000000 | 0.000000 |
| removing tmp table | 0.000004 | 0.000000 | 0.000000 |
| closing tables | 0.000010 | 0.000000 | 0.000000 |
| freeing items | 0.000176 | 0.001000 | 0.000000 |
| cleaning up | 0.000041 | 0.000000 | 0.000000 |
+--------------------------------+----------+----------+------------+
31 rows in set, 1 warning (0.00 sec)

サロゲートキーへの依存度はmax_num * RAND()より小さいけど、なんかどこかに落とし穴がありそうだよなぁ。。

( ´-`).oO(query_cache_type= DEMANDなんだけど、これ切れば300usくらい速くなるな。。


最後に、ランダムピックしたものをそもそもキャッシュする方法も考え付いた。そもそも元の行数が少なければ、ORDER BY RAND()でも戦えるんじゃないか、って話。
キャッシュは定期的に更新(というかスワップ)してやればいい。

mysql56> CREATE TABLE rand_cache AS SELECT num, val FROM t1 ORDER BY RAND() LIMIT 10000;
Query OK, 10000 rows affected (0.17 sec)
Records: 10000 Duplicates: 0 Warnings: 0

mysql56> SELECT * FROM rand_cache ORDER BY RAND() LIMIT 3;
+-------+----------------------------------+
| num | val |
+-------+----------------------------------+
| 2533 | 4de754248c196c85ee4fbdcee89179bd |
| 78915 | d3272a819b09ced96c69e22f183cc88e |
| 16351 | dcb8e02b8527b08dbd8acb146bccc612 |
+-------+----------------------------------+
3 rows in set (0.00 sec)

mysql56> CREATE TABLE tmp_rand_cache AS SELECT num, val FROM t1 ORDER BY RAND() LIMIT 10000;
Query OK, 10000 rows affected (0.14 sec)
Records: 10000 Duplicates: 0 Warnings: 0

mysql56> RENAME TABLE rand_cache TO old_rand_cache, tmp_rand_cache TO rand_cache;
Query OK, 0 rows affected (0.01 sec)

mysql56> DROP TABLE old_rand_cache;
Query OK, 0 rows affected (0.01 sec)

mysql56> SELECT * FROM rand_cache ORDER BY RAND() LIMIT 3;
+-------+----------------------------------+
| num | val |
+-------+----------------------------------+
| 98937 | 73a3320fa46a5e4fad268056af61cd42 |
| 54927 | a11d83c11ed8c95a32b3628a762cf41f |
| 38444 | d3e8129138f2c76dc6e4048281160fe0 |
+-------+----------------------------------+
3 rows in set (0.01 sec)


何も考えないORDER BY RAND()はORDER BY RAND()で、「空振りが無い(WHERE句でフィルターした結果がLIMIT未満でなければ)」、「重複がない」、「サロゲートキーに依存しない」というメリットもあるのだなぁと思った。そりゃあ使いたがる人多くてもわかるは。

複数のテーブルのON UPDATE current_timestampなカラムの値を揃える方法を考える

$
0
0
タイトルで何を言ってるのか我ながら良くわからない。。


mysql56> SELECT * FROM t1 JOIN t2 USING(num);
+-----+-------+---------------------+---------------------+--------+---------------------+---------------------+
| num | val | created | updated | val | created | updated |
+-----+-------+---------------------+---------------------+--------+---------------------+---------------------+
| 1 | one | 2016-03-01 15:38:44 | 2016-03-01 15:38:44 | eins | 2016-03-01 15:38:44 | 2016-03-01 16:40:29 |
| 2 | two | 2016-03-01 15:38:44 | 2016-03-01 15:38:44 | zwei | 2016-03-01 15:38:44 | 2016-03-01 16:40:33 |
| 3 | three | 2016-03-01 15:38:44 | 2016-03-01 15:38:44 | drei | 2016-03-01 15:38:44 | 2016-03-01 16:40:36 |
| 4 | four | 2016-03-01 15:38:44 | 2016-03-01 15:38:44 | vier | 2016-03-01 15:38:44 | 2016-03-01 16:40:59 |
| 5 | five | 2016-03-01 15:38:44 | 2016-03-01 15:38:44 | funf | 2016-03-01 15:38:44 | 2016-03-01 16:41:05 |
| 6 | six | 2016-03-01 15:38:44 | 2016-03-01 15:38:44 | sechs | 2016-03-01 15:38:44 | 2016-03-01 16:41:11 |
| 7 | seven | 2016-03-01 15:38:44 | 2016-03-01 15:38:44 | sieben | 2016-03-01 15:38:44 | 2016-03-01 16:41:16 |
| 8 | eight | 2016-03-01 15:38:44 | 2016-03-01 15:38:44 | acht | 2016-03-01 15:38:44 | 2016-03-01 16:41:27 |
| 9 | nine | 2016-03-01 15:38:44 | 2016-03-01 15:38:44 | neun | 2016-03-01 15:38:44 | 2016-03-01 16:41:34 |
| 10 | ten | 2016-03-01 15:38:44 | 2016-03-01 15:38:44 | zehn | 2016-03-01 15:38:44 | 2016-03-01 16:41:46 |
+-----+-------+---------------------+---------------------+--------+---------------------+---------------------+
10 rows in set (0.00 sec)


t1.updated, t2.updatedはそれぞれDATETIME ON UPDATE CURRENT_TIMESTAMPなカラム。
やりたいことは、

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

mysql56> UPDATE t1 SET val= 'updated' WHERE num = 2;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0

mysql56> UPDATE t2 SET val= 'updated' WHERE num = 2;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0

mysql56> SELECT * FROM t1 JOIN t2 USING(num) WHERE num = 2;
+-----+---------+---------------------+---------------------+---------+---------------------+---------------------+
| num | val | created | updated | val | created | updated |
+-----+---------+---------------------+---------------------+---------+---------------------+---------------------+
| 2 | updated | 2016-03-01 15:38:44 | 2016-03-01 16:43:35 | updated | 2016-03-01 15:38:44 | 2016-03-01 16:43:40 |
+-----+---------+---------------------+---------------------+---------+---------------------+---------------------+
1 row in set (0.01 sec)

この時点でt1.updatedとt2.updatedを同じ時刻にすること。



1. リテラル渡す

mysql56> ROLLBACK AND CHAIN;
Query OK, 0 rows affected (0.00 sec)

mysql56> UPDATE t1 SET val= 'updated', updated= '2016-03-01 16:44:05' WHERE num = 2;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0

mysql56> UPDATE t2 SET val= 'updated', updated= '2016-03-01 16:44:05' WHERE num = 2;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0

mysql56> SELECT * FROM t1 JOIN t2 USING(num) WHERE num = 2;
+-----+---------+---------------------+---------------------+---------+---------------------+---------------------+
| num | val | created | updated | val | created | updated |
+-----+---------+---------------------+---------------------+---------+---------------------+---------------------+
| 2 | updated | 2016-03-01 15:38:44 | 2016-03-01 16:44:05 | updated | 2016-03-01 15:38:44 | 2016-03-01 16:44:05 |
+-----+---------+---------------------+---------------------+---------+---------------------+---------------------+
1 row in set (0.00 sec)

はじめっからUPDATE ON current_timestampなんてつけないでほしかった。


2. 1ステートメントで更新する

mysql56> ROLLBACK AND CHAIN;
Query OK, 0 rows affected (0.00 sec)

mysql56> UPDATE t1 JOIN t2 USING(num) SET t1.val= 'updated', t2.val= 'updated' WHERE t1.num = 2;
Query OK, 2 rows affected (0.00 sec)
Rows matched: 2 Changed: 2 Warnings: 0

mysql56> SELECT * FROM t1 JOIN t2 USING(num) WHERE num = 2;
+-----+---------+---------------------+---------------------+---------+---------------------+---------------------+
| num | val | created | updated | val | created | updated |
+-----+---------+---------------------+---------------------+---------+---------------------+---------------------+
| 2 | updated | 2016-03-01 15:38:44 | 2016-03-01 16:47:24 | updated | 2016-03-01 15:38:44 | 2016-03-01 16:47:24 |
+-----+---------+---------------------+---------------------+---------+---------------------+---------------------+
1 row in set (0.00 sec)

CURRENT_TIMESTAMP()はNOW()のシノニムなので、そのまま使うと「ステートメント開始時点の現在時刻」を返す。
という訳で1ステートメントなら開始時刻は1つに定まる。

どう考えても綺麗にJOINできないテーブルとかあるしINSERTやDELETEが混じったトランザクションで詰むので却下。


3. timestamp変数使う

さらに、SET TIMESTAMP ステートメントによって、NOW() で返された値は影響を受けますが、SYSDATE() で返された値は影響を受けません。つまり、バイナリログのタイムスタンプ設定は、SYSDATE() の呼び出しに影響しないことを意味します。タイムスタンプをゼロ以外の値に設定すると、後続の NOW() が起動されるたびに、その値が返されます。タイムスタンプをゼロに設定すると、この効果が取り消され、再度 NOW() が現在の日付と時間を返すようになります。

http://dev.mysql.com/doc/refman/5.6/ja/date-and-time-functions.html#function_now

というわけでこうじゃ。


mysql56> ROLLBACK AND CHAIN;
Query OK, 0 rows affected (0.00 sec)

mysql56> SET timestamp= @@timestamp;
Query OK, 0 rows affected (0.00 sec)

mysql56> UPDATE t1 SET val= 'updated' WHERE num = 2;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0

mysql56> UPDATE t2 SET val= 'updated' WHERE num = 2;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0

mysql56> SELECT * FROM t1 JOIN t2 USING(num) WHERE num = 2;
+-----+---------+---------------------+---------------------+---------+---------------------+---------------------+
| num | val | created | updated | val | created | updated |
+-----+---------+---------------------+---------------------+---------+---------------------+---------------------+
| 2 | updated | 2016-03-01 15:38:44 | 2016-03-01 16:55:46 | updated | 2016-03-01 15:38:44 | 2016-03-01 16:55:46 |
+-----+---------+---------------------+---------------------+---------+---------------------+---------------------+
1 row in set (0.00 sec)

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

mysql56> SET timestamp= 0;
Query OK, 0 rows affected (0.00 sec)

コミットした後は(前でもいいけど)timestamp変数を0に戻しておかないと、そのコネクションのNOW()がいつまでもSET timestampした時点の時刻を返すようになる。


…はじめっからUPDATE ON current_timestampなんてつけないでほしかった。

MySQL = 5.7.11に乗り換えるだけでdefault_password_lifetimeの呪縛から逃れられる理由

$
0
0
見直したらほぼ 日々の覚書: MySQL 5.7.4で導入されたdefault_password_lifetimeがじわじわくる(MySQL 5.7.11でFIX!!) に書いてあったんだけど、



default_password_lifetimeはユーザー作成時には何もせず、ユーザーがログインするたびにpassword_last_changedと比較するので、default_password_lifetime= 360でユーザーを作っちゃっても今の値が0なら特に何もする必要はないです

文字コードの話でいうと、character_set_serverのデフォルトにあたるものが置き換わっただけで、テーブル単位で既に指定されている文字コードは変わらない、というのと同じ感じ。

default_password_lifetimeは「ユーザーを作ってからn日後にEXPIREする」ではなくて(変数の名前からするとそんな動作しそうなんだけど)、「ログイン試行時にpassword_lifetimeが明示的に決められていない場合、password_last_changedと現在時刻を比較して、n日以上経過してたらエラーを返す」ためのパラメーター。


mysql57> SELECT user, host, password_expired, password_last_changed, password_lifetime FROM user;
+-----------+-----------+------------------+-----------------------+-------------------+
| user | host | password_expired | password_last_changed | password_lifetime |
+-----------+-----------+------------------+-----------------------+-------------------+
| root | localhost | N | 2016-02-07 21:31:49 | NULL |
| mysql.sys | localhost | N | 2016-02-07 21:31:49 | NULL |
+-----------+-----------+------------------+-----------------------+-------------------+
2 rows in set (0.00 sec)

関係してくるカラムはこのへん。

まず、password_lifetimeが NULLならuse_default_password_lifetimeのフラグが立つ。NULL以外の場合(EXPIREする日付が入る)はuse_default_password_lifetimeのフラグは降りる。これはacl_loadの中なので、mysqldが起動した時やFLUSH PRIVILEGESの時にこの処理を通る。

https://github.com/mysql/mysql-server/blob/7ef2156f065d388f2c7ba2e0a69b3610e417f4d6/sql/auth/sql_auth_cache.cc#L1764-L1798


それから認証時のcheck_password_lifetime。

1) password_expiredが'Y'ならreturn false
2) use_default_password_lifetimeフラグが降りてたらpassword_lifetimeと現在時刻を比較。過ぎてたらtrue
3) フラグが降りてなかったらpassword_last_changedと現在時刻とdefault_password_lifetimeで比較。過ぎてたらtrue

https://github.com/mysql/mysql-server/blob/7ef2156f065d388f2c7ba2e0a69b3610e417f4d6/sql/auth/sql_authentication.cc#L1988-L2028


check_password_lifetimeの呼び出し元では、password_expiredが'Y'またはcheck_password_lifetimeの戻り値がtrueならEXPIREされているとしてER_MUST_CHANGE_PASSWORD_LOGIN。

https://github.com/mysql/mysql-server/blob/7ef2156f065d388f2c7ba2e0a69b3610e417f4d6/sql/auth/sql_authentication.cc#L2358-L2381


CREATE USER時に通るのはmysql_create_userだけれど、ここは特にdefault_password_lifetimeはチェックしてない。ALTER USERとSET PASSWORDの時だけ変数を参照してるくらい。

https://github.com/mysql/mysql-server/blob/7ef2156f065d388f2c7ba2e0a69b3610e417f4d6/sql/auth/sql_user.cc#L1232


5.7.11の時点で、という感じなので、5.8以降で再び仕掛けてくる時はどうなるか知らない。

SHOW TABLE STATUSのData_lengthとかIndex_lengthとかData_freeの値をぼんやり考える

$
0
0
InnoDBの場合。MyISAMは全くアテにならなかった(少なくとも5.7.11では)
InnoDBでも所詮統計情報なので完全にアテになる訳じゃないのはお約束。

テスト。


mysql57> CREATE TABLE t1 (val char(250));
Query OK, 0 rows affected (0.02 sec)

mysql57> INSERT INTO t1 SET val= '';
Query OK, 1 row affected (0.00 sec)

mysql57> INSERT INTO t1 SELECT * FROM t1;
Query OK, 1 row affected (0.00 sec)
Records: 1 Duplicates: 0 Warnings: 0

..

mysql57> INSERT INTO t1 SELECT * FROM t1;
Query OK, 131072 rows affected (1.67 sec)
Records: 131072 Duplicates: 0 Warnings: 0

ysql57> ANALYZE TABLE t1;
+-------+---------+----------+----------+
| Table | Op | Msg_type | Msg_text |
+-------+---------+----------+----------+
| d1.t1 | analyze | status | OK |
+-------+---------+----------+----------+
1 row in set (0.00 sec)

mysql57> SHOW TABLE STATUS\G
*************************** 1. row ***************************
Name: t1
Engine: InnoDB
Version: 10
Row_format: Compact
Rows: 257368
Avg_row_length: 312
Data_length: 80330752
Max_data_length: 0
Index_length: 0
Data_free: 4194304
Auto_increment: NULL
Create_time: 2016-03-30 19:14:45
Update_time: 2016-03-30 19:24:46
Check_time: NULL
Collation: utf8mb4_general_ci
Checksum: NULL
Create_options: row_format=Dynamic
Comment:
1 row in set (0.01 sec)

mysql57> SELECT table_rows, sys.format_bytes(data_length) AS data_length, sys.format_bytes(index_length) AS index_length, sys.format_bytes(data_free) AS data_free FROM information_schema.tables WHERE (table_schema, table_name)= ('d1', 't1');
+------------+-------------+--------------+-----------+
| table_rows | data_length | index_length | data_free |
+------------+-------------+--------------+-----------+
| 257368 | 76.61 MiB | 0 bytes | 4.00 MiB |
+------------+-------------+--------------+-----------+
1 row in set (0.01 sec)

SHOW TABLE STATUSだと見にくいのでi_s使って取ってみる。
innodb_autoextend_incrementはシステムテーブルスペース(ibdata1)の自動拡張単位で、.ibdファイルの場合は最大で4MB(らしい)

https://dev.mysql.com/doc/refman/5.6/ja/innodb-parameters.html#sysvar_innodb_autoextend_increment

100行ずつINSERTしながら100万行くらいまで様子を見てみたけど、Data_freeはだいたい4MB~7MBの間を彷徨っている様子。4MBまでは利用可能領域を使って、それを割るとautoextendするっぽい。

行を半分くらい消してみる。


mysql57> DELETE FROM t1 WHERE rand() < 0.5;
Query OK, 130891 rows affected (1.00 sec)

mysql57> ANALYZE TABLE t1;
+-------+---------+----------+----------+
| Table | Op | Msg_type | Msg_text |
+-------+---------+----------+----------+
| d1.t1 | analyze | status | OK |
+-------+---------+----------+----------+
1 row in set (0.00 sec)

mysql57> SELECT table_rows, sys.format_bytes(data_length) AS data_length, sys.format_bytes(index_length) AS index_length, sys.format_bytes(data_free) AS data_free FROM information_schema.tables WHERE (table_schema, table_name)= ('d1', 't1');
+------------+-------------+--------------+-----------+
| table_rows | data_length | index_length | data_free |
+------------+-------------+--------------+-----------+
| 131354 | 76.61 MiB | 0 bytes | 4.00 MiB |
+------------+-------------+--------------+-----------+
1 row in set (0.00 sec)

Data_lengthは変わらず、Data_freeも変わらず。.ibdファイルのサイズももちろん変わらない。


mysql57> INSERT INTO t1 SELECT * FROM t1 WHERE rand() < 0.5;
Query OK, 65675 rows affected (0.77 sec)
Records: 65675 Duplicates: 0 Warnings: 0

mysql57> ANALYZE TABLE t1;
+-------+---------+----------+----------+
| Table | Op | Msg_type | Msg_text |
+-------+---------+----------+----------+
| d1.t1 | analyze | status | OK |
+-------+---------+----------+----------+
1 row in set (0.00 sec)

mysql57> SELECT table_rows, sys.format_bytes(data_length) AS data_length, sys.format_bytes(index_length) AS index_length, sys.format_bytes(data_free) AS data_free FROM information_schema.tables WHERE (table_schema, table_name)= ('d1', 't1');
+------------+-------------+--------------+-----------+
| table_rows | data_length | index_length | data_free |
+------------+-------------+--------------+-----------+
| 193161 | 76.42 MiB | 0 bytes | 4.00 MiB |
+------------+-------------+--------------+-----------+
1 row in set (0.00 sec)

消したぶんより少ない行をINSERTすると、Data_lengthは伸びない(= 空きページが再利用されている)けどData_freeも変わらない。謎い(deleteした時にdata_freeをインクリメントしてた気がするんだけど見付けられない。気のせいだったのか)
パージスレッドのご機嫌なのかなぁと思ってinnodb_fast_shutdown= 0にして再起動してみたけど変わらない。WHEREなしのDELETEでどかんと行くとData_freeに全部計上されるんだけどなぁ。


mysql57> SET GLOBAL innodb_fast_shutdown= 0;
Query OK, 0 rows affected (0.00 sec)

mysql57> SHUTDOWN;
Query OK, 0 rows affected (0.00 sec)

$ mysqld_multi start 57


Index_lengthも同じ様子。

mysql57> ALTER TABLE t1 ADD KEY (val);
Query OK, 0 rows affected (9.73 sec)
Records: 0 Duplicates: 0 Warnings: 0

mysql57> ANALYZE TABLE t1;
+-------+---------+----------+----------+
| Table | Op | Msg_type | Msg_text |
+-------+---------+----------+----------+
| d1.t1 | analyze | status | OK |
+-------+---------+----------+----------+
1 row in set (0.01 sec)

mysql57> SELECT table_rows, sys.format_bytes(data_length) AS data_length, sys.format_bytes(index_length) AS index_length, sys.format_bytes(data_free) AS data_free FROM information_schema.tables WHERE (table_schema, table_name)= ('d1', 't1');
+------------+-------------+--------------+-----------+
| table_rows | data_length | index_length | data_free |
+------------+-------------+--------------+-----------+
| 191498 | 76.44 MiB | 58.98 MiB | 4.00 MiB |
+------------+-------------+--------------+-----------+
1 row in set (0.00 sec)

mysql57> DELETE FROM t1 WHERE rand() < 0.5;
Query OK, 98257 rows affected (1.94 sec)

mysql57> ANALYZE TABLE t1;
+-------+---------+----------+----------+
| Table | Op | Msg_type | Msg_text |
+-------+---------+----------+----------+
| d1.t1 | analyze | status | OK |
+-------+---------+----------+----------+
1 row in set (0.02 sec)

mysql57> SELECT table_rows, sys.format_bytes(data_length) AS data_length, sys.format_bytes(index_length) AS index_length, sys.format_bytes(data_free) AS data_free FROM information_schema.tables WHERE (table_schema, table_name)= ('d1', 't1');
+------------+-------------+--------------+-----------+
| table_rows | data_length | index_length | data_free |
+------------+-------------+--------------+-----------+
| 92680 | 76.44 MiB | 58.98 MiB | 4.00 MiB |
+------------+-------------+--------------+-----------+
1 row in set (0.00 sec)


大体、storage/innobase/handler/i_s.ccから上手く見つけられないんだけどどこから見つければいいんだっけ。。:(;゙゚'ω゚'):
Viewing all 581 articles
Browse latest View live