Hatena::ブログ(Diary)

do_akiの徒然想記 RSSフィード

2015-11-25

Casualに闇とBLTしてきました

  • 2015/11/20 MySQL Casual Talks vol.8
  • 2015/11/22 第六回闇PHP勉強会
  • 2015/11/24 PHP BLT #1

意図せず、飛び石でトークすることになってました。

MySQL Casual Talks vol.8

いつものアレに対する近況報告と、ちょろっと Multi-source Replication の話。

それに加えて、 Optimizer Hint の話をしました。

実は Optimizer Hint の話はおまけで、

no title

こっちのほうがメインだったりする。

第二回闇PHP勉強会

PHP7 で導入された、コンパイル時に構築される抽象構文木についての話。

nikic/php-ast を知らずに、 ast 構造を確認するための拡張書いてしまったので、せっかくなので DEMO しました。

DEMO で使ったアプリケーションは、雑に書いたものなので実行できるものを公開するのは控えますが、コードは github にあります。

自分で動かすなら、 https://github.com/do-aki/phpast を手元に持ってきて以下の手順で動かせます。graphviz 必要だけど。

$ phpize
$ ./configure
$ make
$ php -d extension=`pwd`/modules/phpast.so -S localhost:3000 -t webapp/

PHP BLT#1

PHP拡張をGo言語で書いてみたよという話。

発表する時になって気づいたのですが、このトーク、php色まったくないですね。

どなたか、c-shared で build する場合に go runtime 部分にデバッグ情報を付与する方法を教えてください。

まとめ

このペースでネタ作って資料作ってトークするの結構しんどかった。

Link

MySQL Casual Talks vol.8
http://eventdots.jp/event/574060
第六回闇PHP勉強会
https://yamiphp.doorkeeper.jp/events/33702
PHP BLT #1
http://phpblt.connpass.com/event/21929/

2014-10-14

PHPカンファレンス2014 で「mysqlnd 徹底解説」を話してきたこと #phpcon2014

去る 2014/10/11。大田区産業プラザPiOにて PHPカンファレンス2014が開催されました。

その中で、「mysqlnd 徹底解説」というタイトルでトークさせていただきました。

雑感

思い返せば PHP カンファレンスで初LT をしたのが 2011年。

いつかLTではなく本編で話そうと思いつつ、気づけば3年経っていました。

相変わらず行動力に欠けるようで。

応募したのは完全にノリ。


けど、採択されてからが大変だった。

採択されたのが9月25日、2週間ちょいしかない。資料どころか、mysqlnd についてまだ全然分かってない。という状況。

応募したときはちょうど仕事で mysqlnd の挙動について軽く追っていた頃だったので「これきっかけに詳細にコード追ってみよう」という軽い気持ちだったのですが、追えば追うほどよく分からないコードが目の前にどんどん詰まれていくという状況。

資料が形になったのは数日前で、前日になって時間が足りない事に気づいて一部削るという判断をしました。

いやー、ホントに発表できるのか心配ダッタヨ。

ただ、今回のセッションの為に歴史や周辺のこと(ライセンス問題とか)を深く調べたことで、「なぜ今のコードになっているのか」を、だいたい把握できたのはとても大きな収穫だった。

内容について

前半は 「mysqlnd とは何か」について、後半は「mysqlnd で何が変わるのか」についてを中心にトークしました。

前半はスライド見れば分かると思いますが、後半は別途解説がないとわかりにくいと思います。

最後のメモリ爆発のくだりは実際に自分が陥った経験です。サーバーサイドプリペアドステートメント で PDO という、最も地雷なところを踏み抜いてしまったことにより、このセッションが生まれたと言っても過言ではありません。


資料中にはないのですが、実際にはメモリ消費を抑える方法があります。

store_result ではなく、use_result を使うという手法です。

実は、 mysqli においては、プリペアドステートメントをつかうとデフォルトでは use_result となります。

ですので問題が表面しにくくはあるのですが、ただ、mysqli_stmt::store_result や mysqli_stmt::get_result を呼んだ場合は store_result が行われるので注意が必要です。

また PDO の場合は、デフォルトで store_result が使われます。

明示的に use_result を使うためには pdo::setAttribute で明示的に PDO::MYSQL_ATTR_USE_BUFFERED_QUERY を false にする必要があります。(これ、マニュアル読むだけだとデフォルト false っぽいけど、実際にはデフォルトは true)

ただし、use_result を利用すると、そのコネクションに於ける MySQL のリソースを占有するため、結果セットを解放するまで次のクエリを実行することが出来ません。また、フェッチの度に MySQL にアクセスするため、その分 MySQL 側の負担が増えます。

必要に応じて使い分けるのが良いでしょう。

とはいえ、それぞれをラップしたライブラリとかだと、なかなか難しいんだよねこれ。


レポート記事への補足

PHPカンファレンス2014 当日レポート[更新終了]:PHPカンファレンス2014 スペシャルレポート|gihyo.jp … 技術評論社 でレポート記事がでています。

結構詰め込んだセッションだったと思うのですが、要点要点しっかり抑えられていて素晴らしいです。

が、一部誤った部分があるので補足。


誤: libmysqlはGPLv2という若干特殊なライセンスであり,同梱して別のところに配布ができない

正: libmysqlはFOSS例外付きGPLv2という若干特殊なライセンスであり, ソースコードの開示なしに同梱して頒布出来ない


この辺は自分自身しっかりと把握している訳で確実ではないのですが、

libmysqlclient を (商用ライセンスではなく) GPLv2 として利用した場合、それを利用した php スクリプトプロプライエタリとすることは出来ない という認識です。

ライセンス ムズカシイネー


「PHPコアから読み解くPHP5.5」を聴いて

yield 使ったコードと使わないコードどっちが速い? -> ほとんど変わらないから yield 使った方がコードがすっきりしていいよね

(bool)$value と boolval($value) どっちが速い? -> キャストの方が早いからキャスト使おうね

zend_execute -> 5.4系までと5.5からとで異なるから注意な

という内容。

コアから読み解く、というわりにコアの一部だけだった感は否めない。

オペコードの数では速度は決まらないのにやけに数を強調してたり、具体的な処理時間出す割にベンチの仕方全く出さないしでうーん。


「PHPにおけるI/O多重化とyield」を聴いて

yield には多くの可能性を感じますね

co-httpclient は、http request に特化しているけれど、event loop っぽいところは汎用化できると思うので、

具体処理を差し込むかたちにできると面白いかな−と思った。

perl の Coro 思い出すよね。

「HHVM + Hack == PHP++」を聴いて

久々にわくわくするセッションだった。

静的型付けで強力な型推論あれば最高だよねーな人間としては、こういう方向性ってのはホント面白い。


プログラミング言語って人間が扱うモノだから、正しい方向って一つじゃないんだよねーと思ってる。

そういう意味でも、facebook が php script のほとんどを hack に置き換えた後、 hack に何を加えていこうとしていくのかは気になるところ。


LT 「よいことも悪いこともぜんぶPHPが教えてくれた」を聴いて

これはもう、じっくりと時間をかけて聴きたかった。

クロージングの前にこれを基調講演として枠取ってやって欲しかった。

http://d.hatena.ne.jp/moriyoshi/20110204/1296808771 と、この続きを含めてどっかで講演して欲しい。

哲学がないという哲学」なんという哲学や……。

最後に

PHP カンファレンス実行委員の皆様、お疲れ様でした&ありがとうございました。

またセッションを聞きに来てくださった方、ありがとうござました。


リンク

PHPコアから読み解くPHP5.5
http://www.slideshare.net/techblogyahoo/phpphp55
PHPにおけるI/O多重化とyield
https://joind.in/talk/view/12038
Cooperative multitasking using coroutines (in PHP!)
http://nikic.github.io/2012/12/22/Cooperative-multitasking-using-coroutines-in-PHP.html
よいことも悪いこともぜんぶPHPが教えてくれた
http://www.slideshare.net/moriyoshi/php-40137945
今さらだけどMySQLとライセンス
http://www.slideshare.net/h141/mysql-22023284
漢(オトコ)のコンピュータ道
FOSS License Exception:http://nippondanji.blogspot.jp/2009/05/foss-license-exception.html

2013-12-04

UNSIGNED の不思議な挙動 #mysqlcasual

かじゅある!

この記事は、MySQL Casual Advent Calendar 2013 4日目です。

3日目を数秒差で @kamipo さんに取られてしまい*1、ネタかぶったらどうしようとドキドキしていましたが、そのようなことはなくこれでようやく安眠できそうです。


MySQL では、UNSIGNED が使える

UNSIGNED は、標準SQL にはないデータ型属性です。*2

通常の INT が -2147483648 から 2147483647 の範囲を表すのに対し、

INT UNSIGNED で定義したカラムは 0 から 4294967295 の範囲となります。


今回は、この UNSIGNED に関する奇妙な挙動のお話。


なお、検証に利用したバージョンは 5.1.69 です。

すでに 5.6 が GA となっている現在からするとやや古いバージョンではありますが、REHL 6.5 で採用されているバージョンでもあるので、これを利用している方はまだ多いのではないでしょうか。

突然の 4294967295

こんなテーブルがあるとしましょう。

CREATE TABLE test(
  id VARCHAR(10) PRIMARY KEY,
  i INT,
  ui INT UNSIGNED
);
INSERT INTO test VALUES
  ('max',2147483647, 4294967295),
  ('min',-2147483648, 0);

SELECT * FROM test;
+-----+-------------+------------+
| id  | i           | ui         |
+-----+-------------+------------+
| max |  2147483647 | 4294967295 |
| min | -2147483648 |          0 |
+-----+-------------+------------+

id は分かりやすさのために付けただけなので、注目すべきは i (INT) と ui (UNSIGNED INT)

それぞれ max には最大値、min には 最小値 が格納されています。

ここで、範囲を超えるような演算をしてみましょう。

UPDATE test SET i=i+1, ui=ui+1 WHERE id='max';
UPDATE test SET i=i-1, ui=ui-1 WHERE id='min';

Warning はでますが、エラーとはならずに実行出来ます。

そして結果がこれ。

SELECT * FROM test;
+-----+-------------+------------+
| id  | i           | ui         |
+-----+-------------+------------+
| max |  2147483647 | 4294967295 |
| min | -2147483648 | 4294967295 |
+-----+-------------+------------+


( ゚д゚)……(つд⊂)ゴシゴシ…… えっ?



なぜか INT UNSIGNED だけ、最小値から減算すると 4294967295 になる。


上記例では 1 を引いてますが、いくつを引いても 4294967295 でした。

UPDATE test SET ui=0 WHERE id='min';          -- 一度 0 に戻して
UPDATE test SET ui=ui-5000 WHERE id='min';    -- 大きい数字を引いても
SELECT * FROM test;
+-----+-------------+------------+
| id  | i           | ui         |
+-----+-------------+------------+
| max |  2147483647 | 4294967295 |
| min | -2147483648 | 4294967295 |
+-----+-------------+------------+

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

わけが分からない。



数値を文字列で渡してみる

先ほどのテーブルを一度初期値(最大値/最小値)に戻します。

UPDATE test SET i=2147483647, ui=4294967295 WHERE id='max';
UPDATE test SET i=-2147483648, ui=0 WHERE id='min';
SELECT * FROM test;
+-----+-------------+------------+
| id  | i           | ui         |
+-----+-------------+------------+
| max |  2147483647 | 4294967295 |
| min | -2147483648 |          0 |
+-----+-------------+------------+

今度は、数値をシングルクォテーションで囲って、文字列で渡してみましょう。

UPDATE test SET i=i+'1', ui=ui+'1' WHERE id='max';
UPDATE test SET i=i-'1', ui=ui-'1' WHERE id='min';

前回同様 Warning はでるものの、実行はできます。

シングルクォテーションで囲ったところで数値として解釈されるはず。

なので挙動は変わらないはず……と思って結果を見ると

SELECT * FROM test;
+-----+-------------+------------+
| id  | i           | ui         |
+-----+-------------+------------+
| max |  2147483647 | 4294967295 |
| min | -2147483648 |          0 |
+-----+-------------+------------+


( ゚д゚)……(つд⊂)ゴシゴシ…… えっ?



なぜかこちらは、保存されたデータが変化することはありません。

や、むしろこの方が適切な挙動のように思えます。

けど、なぜ 数値を文字列にするだけで挙動変わる???

わけが分からない。



5.7 で試したら

5,7 で試してみたところ、データが書き換わらない動きに統一されているようです。

「突然の 4294967295」となるクエリについては エラーとなりました。(SET sql_mode='' にて)

エラー文言を見る限り、格納される前に演算の部分でエラーとなっているように見えます。

mysql> SELECT version();
+---------------+
| version()     |
+---------------+
| 5.7.2-m12-log |
+---------------+
1 row in set (0.00 sec)

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

mysql> UPDATE test SET i=i-1, ui=ui-1 WHERE id='min';
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '(`test`.`test`.`ui` - 1)'

とはいえ、5.6 以降はsql_mode にデフォルトで STRICT_TRANS_TABLES が含まれている*3ので、あまり問題にはならないかと思います。

範囲外の値が格納されるケースは全て エラーとなって SQL の実行に失敗しますので。


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

mysql> UPDATE test SET i=i+1, ui=ui+1 WHERE id='max';
ERROR 1264 (22003): Out of range value for column 'i' at row 1
mysql> UPDATE test SET i=i-1, ui=ui-1 WHERE id='min';
ERROR 1264 (22003): Out of range value for column 'i' at row 1

まとめ

5.1でも STRICT_ALL_TABLES を設定したほうが良いんじゃないかな。

とはいえ、既に動いてるシステムで sql_mode 変えるのはしんどいので、おかしなクエリは発行しないよう注意しましょー。

明日は @yoshi_ken さんです。




かじゅある!

*1https://twitter.com/kamipo/status/400596752798994432 / https://twitter.com/do_aki/status/400596814052593665

*2:MySQL :: MySQL 5.1 リファレンスマニュアル :: 1.8.4 SQL標準に対するMySQL拡張機能 http://dev.mysql.com/doc/refman/5.1/ja/extensions-to-ansi.html

*3:日々の覚書: MySQL5.6が勝手にsql_modeを書き換えてくれる話 http://yoku0825.blogspot.jp/2013/03/mysql56sqlmode.html

2012-04-20

MySQL Casual Talks Vol.3 で超LT しました

1分間の Lightning Talk 。実際には数秒超過したのは内緒。

早口すぎて(そして滑舌が悪くて)聞き取ってもらえなかったんじゃないかという心配もあったり。

そんなこんなで、今も N:1 レプリケーションは現役稼働中ですよ。というだけの内容でした。

発表は疲れたよ。。。

スライド内のリンク一覧

2012-04-15

mysqlでテーブルのサイズを取得する方法

SHOW TABLE STATUS でもとれるんですが、1テーブルずつしかとれません。

information_schema にあったような気がしたので調べてみたらやっぱりありました。


SELECT table_schema, table_name, data_length, index_length 
FROM information_schema.tables

table_schema が スキーマ名で、table_name がテーブル名。

SQL なので、WHERE 句を自由に指定できるし、集計するのも簡単。


ただ、 information_schema 自体は SQL 標準なのだけど、data_length や index_length は MYSQL 独自拡張。

案の定、PostgreSQL にはなかった。

リンク

2012-02-21

MySQL のエラーログを削除する際の注意点

とある事情により、 MySQL (バージョンは 5.5.13)の エラーログが溜まってしまったのでパージしようとした時のこと。

さすがに、そのままエラーログを削除するのは拙いよなーと思い、探してみた。


5.1 (日本語版) のマニュアルではだめ

リファレンスを参照すると ref.1 を見つけたので、 FLUSH LOGS を打ってみたところ、エラーログファイルのリネームされず。。。

元のファイル名のまま、エラーが吐き続けられる。

ref.1 FLUSH LOGS (5.1) の解説

もしサーバが --log-error オプションでスタートされたら、それは FLUSH LOGS によって -old のサフィックスを利用して現在のエラー ログ ファイルをリネームし、新しく空のログ ファイルを作成します。もし --log-error オプションがなければリネームは行われません。

(http://dev.mysql.com/doc/refman/5.1/ja/flush.html より抜粋)


まぁ、こいつは 5.1 の解説なので、気を取り直して 5.5 のリファレンスを見てみることにする。

同じ FLUSH 構文のページを参照すると、Section 5.2.2, “The Error Log”を参照しろとあるので、そちらを見る。

5.5 (英語版) のマニュアルを参照、そして解決

ref.2 エラーログのフラッシュについて (5.5)

If you flush the logs using FLUSH LOGS or mysqladmin flush-logs and mysqld is writing the error log to a file (for example, if it was started with the --log-error option), the effect is version dependent:

(http://dev.mysql.com/doc/refman/5.5/en/error-log.html より抜粋)


どうやら、バージョンによって挙動が異なるらしい。

バージョン 5.5.7 からはリネームしなくなったので、手動でリネームしてから FLUSH してね。とのこと。

mv してから、 FLUSH ERROR LOGS (5.5 系なら、エラーログに限定して FLUSH することが出来る) を叩いてやると、無事、切り離されましたとさ。

リネームしただけだと、そのファイルに吐かれ続けるので FLUSH は忘れずに。


念のため、 5.1 (英語)のマニュアルを参照

さて、ふと思うところあって、英語版のマニュアルを参照してみると…… あれ? 5.5と同じ記述が (ref.3)。

日本語のマニュアルだと、リネームされるとの表記しかなかったのだけど、

英語のマニュアル見ると、 5.1.51 からはリネームされなくなっているとの表記があることが分かる。

5.1系では実際に試してないけど、たぶん英語版が正しいんじゃないかなぁ。

日本語のマニュアルは更新されないままの箇所がちらほら見受けられるし。

ref.3 エラーログのフラッシュについて (5.1)

If you flush the logs using FLUSH LOGS or mysqladmin flush-logs and mysqld is writing the error log to a file (for example, if it was started with the --log-error option), the effect is version dependent:

(http://dev.mysql.com/doc/refman/5.1/en/error-log.html より抜粋)



まとめ

  • FLUSH (ERROR) LOGS 実行時の挙動はバージョンによって異なるので注意。
    • 5.5 系の場合
      • 5.5.7 以降 : リネームなしで再オープン
      • 5.5.7 より前 : それまでのログをリネーム (末尾に -old ) して従来のファイル名で再オープン
    • 5.1 系の場合
      • 5.1.51 以降 : リネームなしで再オープン
      • 5.1.51 より前 : それまでのログをリネーム (末尾に -old ) して従来のファイル名で再オープン
  • 正しいバージョンのマニュアルを参照しよう。
  • 日本語版は参考にとどめて、英語版のマニュアルをちゃんと見よう。

2011-12-21

Advent Calendar 21日目 MySQL において1つのクエリでカラムの値を入れ替える方法

たぶん滅多に使うことはないと思うけれども、必要になって調べたことがあったので掲載。

sample table
CREATE TABLE `sample` (
  `id` int(10) unsigned NOT NULL auto_increment,
  `a` int(10) default NULL,
  `b` int(10) default NULL,
  PRIMARY KEY  (`id`)
);
mysql> select * from sample;
+----+------+------+
| id | a    | b    |
+----+------+------+
|  1 |    1 |    2 |
|  2 |    3 |    4 |
|  3 |    5 |    6 |
|  4 |    7 |    8 |
|  5 |    9 |   10 |
+----+------+------+
5 rows in set (0.00 sec)

例えばこんなテーブルがあった時、

カラム a と カラム b の値を入れ替えたい場合は、以下のようなクエリを発行すればいい。

入れ替えクエリ
UPDATE sample SET a = IF((@tmp:=a), b, b), b = @tmp;

実行結果
mysql> UPDATE sample SET a = IF((@tmp:=a), b, b), b = @tmp;
Query OK, 5 rows affected (0.02 sec)
Rows matched: 5  Changed: 5  Warnings: 0

mysql> select * from sample;
+----+------+------+
| id | a    | b    |
+----+------+------+
|  1 |    2 |    1 |
|  2 |    4 |    3 |
|  3 |    6 |    5 |
|  4 |    8 |    7 |
|  5 |   10 |    9 |
+----+------+------+
5 rows in set (0.00 sec)

あれ? このネタの方がカジュアルじゃなかった!?