がるの健忘録

エンジニアでゲーマーで講師で占い師なおいちゃんのブログです。

PDO関連備忘録

ちと確認したいことがあったので。
もっそメモ書きなので、適宜脳内保管しつつ読んでくださりませ。


とりあえず

set global long_query_time = 0;
set global slow_query_log = 'on';

して、MySQL側に渡っているものを覗いてみようかなぁとか画策。

create table test(
  a int,
  b varbinary(16),
  c blob,
  d DOUBLE,
  e datetime
);

という、大変にやる気のあふれたテーブルを作成w


以下

<?php
$o = new PDO('mysql:dbname=test;host=localhost', ユーザアカウント, ぱすわぁど);
$o->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

を前提に。

$sql = 'INSERT INTO test(a,b,c,d,e) VALUES(:a, :b, :c, :d, :e);';
$pp = $o->prepare($sql);

// intをintで
$pp->bindValue(':a', 1, PDO::PARAM_INT);
$pp->execute();

// intをSTRで
$pp->bindValue(':a', 2, PDO::PARAM_STR);
$pp->execute();

// intをLOBで
$pp->bindValue(':a', 3, PDO::PDO::PARAM_LOB);
$pp->execute();

ってテストしたら

Invalid parameter number: number of bound variables does not match number of tokens

って怒らりた。パラメタ数、結構まじめにチェックしてやがんの。てっきり「無指定はNULL」とかぬるめな事考えてたのに。


気を取り直して。

$sql = 'INSERT INTO test(a) VALUES(:a);';
$pp = $o->prepare($sql);

// intをintで
$pp->bindValue(':a', 1, PDO::PARAM_INT);
$pp->execute();

// intをSTRで
$pp->bindValue(':a', 2, PDO::PARAM_STR);
$pp->execute();

// intをLOBで
$pp->bindValue(':a', 3, PDO::PDO::PARAM_LOB);
$pp->execute();

あんまり取り直してない気もするけど気にしない。


結果。

INSERT INTO test(a) VALUES(1);
INSERT INTO test(a) VALUES('2');
INSERT INTO test(a) VALUES(3);

まぁ予想通り。

select * from test;

                                                                      • +
a b c d e
                                                                      • +
1 NULL NULL NULL NULL
2 NULL NULL NULL NULL
3 NULL NULL NULL NULL
                                                                      • +

こっちもまぁ。


お次。

$sql = 'INSERT INTO test(b) VALUES(:b);';
$pp = $o->prepare($sql);

// intをintで
$pp->bindValue(':b', 1, PDO::PARAM_INT);
$pp->execute();

// intをSTRで
$pp->bindValue(':b', 2, PDO::PARAM_STR);
$pp->execute();

// intをLOBで
$pp->bindValue(':b', 3, PDO::PARAM_LOB);
$pp->execute();

コメント直しミス。気にスンナ。

INSERT INTO test(b) VALUES(1);
INSERT INTO test(b) VALUES('2');
INSERT INTO test(b) VALUES(3);

こっちもおおむね。…PARAM_LOBがシングルクォートで囲ってくれてないのが、不思議っちゃぁ不思議。LOBって文字列の系譜ぢゃないのかしらん?

select * from test;

                                                                      • +
a b c d e
                                                                      • +
1 NULL NULL NULL NULL
2 NULL NULL NULL NULL
3 NULL NULL NULL NULL
NULL 1 NULL NULL NULL
NULL 2 NULL NULL NULL
NULL 3 NULL NULL NULL
                                                                      • +


お次は小数点。

$sql = 'INSERT INTO test(a, d) VALUES(:a, :d);';
$pp = $o->prepare($sql);

// intをintで
$pp->bindValue(':a', 1.1, PDO::PARAM_INT);
$pp->bindValue(':d', 1.1, PDO::PARAM_INT);
$pp->execute();

// intをSTRで
$pp->bindValue(':a', 2.2, PDO::PARAM_STR);
$pp->bindValue(':d', 2.2, PDO::PARAM_STR);
$pp->execute();

// intをLOBで
$pp->bindValue(':a', 3.3, PDO::PARAM_LOB);
$pp->bindValue(':d', 3.3, PDO::PARAM_LOB);
$pp->execute();

INSERT INTO test(a,d) VALUES(1.1, 1.1);
INSERT INTO test(a,d) VALUES('2.2', '2.2');
INSERT INTO test(a,d) VALUES(3.3, 3.3);

select * from test;

                                                                      • +
a b c d e
                                                                      • +
1 NULL NULL 1.1 NULL
2 NULL NULL 2.2 NULL
3 NULL NULL 3.3 NULL
                                                                      • +

そ〜だよね〜的な状況。


少しひねる。

$sql = 'INSERT INTO test(a) VALUES(:a);';
$pp = $o->prepare($sql);

// intをintで
$pp->bindValue(':a', 'a', PDO::PARAM_INT);
$pp->execute();

// intをSTRで
$pp->bindValue(':a', 'b', PDO::PARAM_STR);
$pp->execute();

// intをLOBで
$pp->bindValue(':a', 'c', PDO::PARAM_LOB);
$pp->execute();

エラーでるかしらん? むりしゃりintにするかしらん?

INSERT INTO test(a) VALUES('a');
INSERT INTO test(a) VALUES('b');
INSERT INTO test(a) VALUES('c');

…そ〜きたか。ある意味素直だ。

select * from test;

                                                                      • +
a b c d e
                                                                      • +
0 NULL NULL NULL NULL
0 NULL NULL NULL NULL
0 NULL NULL NULL NULL
                                                                      • +

まぁ値が0になるのは当然だらう。


お次は「無指定」のケース。気になるしねぇ。

// あと「無指定」の場合の挙動も確認
$sql = 'INSERT INTO test(a,b,c,d,e) VALUES(:a, :b, :c, :d, :e);';
$pp = $o->prepare($sql);

//
$pp->bindValue(':a', 1);
$pp->bindValue(':b', 2);
$pp->bindValue(':c', 3);
$pp->bindValue(':d', 4);
$pp->bindValue(':e', 5);
$pp->execute();

//
$pp->bindValue(':a', 'a');
$pp->bindValue(':b', 'b');
$pp->bindValue(':c', 'c');
$pp->bindValue(':d', 'd');
$pp->bindValue(':e', 'e');
$pp->execute();

INSERT INTO test(a,b,c,d,e) VALUES('1', '2', '3', '4', '5');
INSERT INTO test(a,b,c,d,e) VALUES('a', 'b', 'c', 'd', 'e');

どうも「文字列でふぉ」らしい。確かどこかにンなこと書いてあったよねぇ、的な。

                                                                                                    • +
a b c d e
                                                                                                    • +
1 2 3 4 0000-00-00 00:00:00
0 b c 0 0000-00-00 00:00:00
                                                                                                    • +


ほんのりとクラックっぽいことを。

$sql = 'INSERT INTO test(b) VALUES(:b);';
$pp = $o->prepare($sql);

//
$pp->bindValue(':b', "'", PDO::PARAM_INT);
$pp->execute();

//
$pp->bindValue(':b', '"', PDO::PARAM_INT);
$pp->execute();

//
$pp->bindValue(':b', '--', PDO::PARAM_INT);
$pp->execute();

INSERT INTO test(b) VALUES('\'');
INSERT INTO test(b) VALUES('\"');
INSERT INTO test(b) VALUES('--');
INSERT INTO test(b) VALUES(';');

まぁめげませんねぇ当然です。

select * from test;

                                                                                                    • +
a b c d e
                                                                                                    • +
NULL ' NULL NULL NULL
NULL " NULL NULL NULL
NULL -- NULL NULL NULL
NULL ; NULL NULL NULL
                                                                                                    • +


最後、今ひとつ不明なのが「PARAM_STMT」。
いくつか情報はチラ見したんだけど、今ひとつ。情報ある人いらっしゃったら教えてくださいませ ノ


さて…本格的にdata_clumpのほうで対応していかないとねぇこの辺。


追記
そういや「使える文字種別」ってどのくらいまでなんだろう?
ハイフン(-)とアンダースコア(_)、できればイコール(=)とアット(@)あたりくらいまでいけるといいんだけどなぁ。
そのうち実験してみませう。

MySQL5.5系インストールメモ

そういえば。cmake使うようになって、わずかながらに面倒だったので、メモ。
installしたのは…2月の頭ごろだったので。バージョンは適宜読み替えてちょ。

wget http://www.cmake.org/files/v2.8/cmake-2.8.7.tar.gz
tar zvxf cmake-2.8.7.tar.gz
cd cmake-2.8.7
./configure
make
sudo make install
cd ../


wget http://ftp.gnu.org/pub/gnu/ncurses/ncurses-5.9.tar.gz
tar zvxf ncurses-5.9.tar.gz
cd ncurses-5.9
./configure
make
sudo make install
cd ../


wget http://dev.mysql.com/get/Downloads/MySQL-5.5/mysql-5.5.20.tar.gz/from/http://ftp.iij.ad.jp/pub/db/mysql/
tar zvxf mysql-5.5.20.tar.gz
cd mysql-5.5.20
vi ./mysql5_ccc
--
rm CMakeCache.txt
make clean
cmake \
  -DCMAKE_INSTALL_PREFIX=/opt/db/mysql-5.5.20 \
  -DDEFAULT_CHARSET=utf8 \
  -DDEFAULT_COLLATION=utf8_general_ci \
  -DWITH_EXTRA_CHARSETS=all \
#  -DWITH_PIC=1

--
sh ./mysql5_ccc
make
sudo make install
sudo ln -s /opt/db/mysql-5.5.20 /opt/db/mysql
cd ../

レプリケーション覚書

MySQLレプリケーション周り。
今度まとめるとして、参考になった数々のURIを列挙。
http://wiki.bit-hive.com/tomizoo/pg/MySQL%20%A5%EC%A5%D7%A5%EA%A5%B1%A1%BC%A5%B7%A5%E7%A5%F3%A4%CE%C0%DF%C4%EA
http://www.irori.org/doc/mysql-rep.html
http://nippondanji.blogspot.com/2009/03/mysql10.html
http://wadslab.net/wiki/index.php?MySQL%20%A5%EC%A5%D7%A5%EA%A5%B1%A1%BC%A5%B7%A5%E7%A5%F3%A4%CE%A5%BB%A5%C3%A5%C8%A5%A2%A5%C3%A5%D7%BC%EA%BD%E7
http://www.limy.org/program/db/mysql/use_replication.html
http://www.maruko2.com/mw/MySQL_%E3%83%AC%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AE%E8%A8%AD%E5%AE%9A
「0から作る」のはそんなに面倒じゃないと思う。やり直しもきくし、せっかくだから色々試してみたいところw
「現在稼働中のサーバをレプリにする」場合は、しっかりと計画を練ってから。ちゃんと作業手順固めておけば、作業自体は10分程度だと思うので、検証と念のための蒔き戻しを想定しても、30分のサービス停止、で、どうにかなると思うんだけどなぁ。
どだろ?


あと、分散接続、LVSでもいいんだけど、興味あるんで「MySQL Proxy」つかってみた。
この辺が参考。
http://nippondanji.blogspot.com/2009/02/mysql-cluster.html
Proxy君、デフォルトのポートが4040(だったか4041だったか)なので。
「ふつ〜」に接続したいときは、起動時の引数をちょっとだけ注意。
あと、「--proxy-backend-addresses」を複数重ねたときは、手前のほうから順繰りに接続するぽ。なので、手前のほうに「比較的パワーのあるサーバ」をおいておくと、ほんの0.1mmほど幸せになれる、かも、しれない。

コンパイルは、こないだは、挫折した orz
今度、腰すえてコンパイルってみたい。


もういっこ。
若干特筆したいところ。
http://arinux.net/?eid=87

read_only # スレーブは read only にします

ん…今まで、アカウントの権限とかでやってたことが多かったんだけど。
my.cnfにこれ書けるんだと、すげぇ楽だなぁ、っておもた。


そのうち、なんかいろんな状況で叩いて、パターン作ってみたいんだけど。
まぁとりあえず一端、備忘録程度に。

ボトルネック見つけ用メモ

なんか色々と資料散らかってるわ忘れてるわで、散々と面倒だったので。
後で付け足す前提で、手元にある「散らかった資料」を整理してまとめておきます的なmemo。


…あぁ先に。set globalとかは「すげぇ激しいパーミッション」が要求されるのでご注意のほどを。
具体的には「SUPER」っちゅぅのが必要です。GRANTで適当に設定しといてちょ。


まず、ログ系の状況の確認。

show variables like 'log%';


特に「重いクエリ」周りの確認。

show global variables like 'slow_query_log'; -- slow_query_log出す? 出さない?
show global variables like 'long_query_time'; -- 何秒以上を「遅い」とする?
show global variables like 'min_examined_row_limit'; -- 「すんげい大量のレコードを求めるクエリ」の大量っておいくつ?


で、コンソールから動的に設定。

set global slow_query_log_file = 'slow_query_logの吐き出し先を指定';
set global long_query_time = 2; -- とりあえず2秒
set global slow_query_log = 1; -- slow log開始!

ちなみに秒数は、小数点とか使えるようになったぽ by 5.1以降。
とりあえずこれ仕込んで「重いクエリ」を探し出しませう。


ただ、もう一つ「なんか細かいけど鬼のようにクエリ投げてる的な?」っていうボトルネックがあるので。
こんな手順で発見。


まずは「全クエリ」を引っ掛けるために、こんな設定。

set global long_query_time = 0;

次に「重いと思われるPage」にアクセス、クエリログを溜め込む。
てけとうに溜め込んだら

set global long_query_time = 2;

とかで一端元に戻す。


そのあと「mysqldumpslow」を使う。これで「似たようなSQL」をカウントしてくれる!!
実際にはこんな感じ。path切れてなかったらてけとうに探すこと。

mysqldumpslow -s t slowログファイル名
mysqldumpslow -s at slowログファイル名

optionがtなのかatなのかは、なんかよくわからなかった。ドッチでも、とりあえず目的は達成できたので、あんまり気にしてないw
これで「ほんのわずかなPageアクセス」で「驚くほど大量のクエリ」があったら、色々と絞殺ぢゃなくて考察をしてみませう。
memcachedとか、こゆときいいよねぇ。MagicWeaponはmemcachedとの相性が「ある程度」いいと思ってるんだけど、他のFrameWorkってどうなんだべさ?


あとは「index使ってない悪い子」を探すあたり。

show global variables like '%indexes';
set global log_queries_not_using_indexes = on;


ついでに、クエリの「受け入れ長さ」の確認と設定変更。

set max_allowed_packet = 1024*1024*32(計算して変換してね);
show variables like 'max_allowed_packet';


思い出したり掘り出したり発掘したりしたら、追記予定。


追記 2012-02-14

set global long_query_time = 2;

やると、いったん「show global variables like 'slow_query_log';」が、OFFになるらしい。注意。
追記追記。これ、誤認かもしれない。確認して、確認取れた人おしえてたも〜。


あと「現在滞留中」のクエリ状況とかを見るなら

SHOW PROCESSLIST; -- 長いクエリは省略表示
SHOW FULL PROCESSLIST; -- 長いクエリも完全に表示

とか。
ちょっとひねると

SELECT * FROM information_schema.PROCESSLIST WHERE time > 10 AND info LIKE 'SELECT%';

とか(10秒以上のんびりこいてるSELECTを探せ)。
これは、元々

DESC information_schema.PROCESSLIST;

で見て取れるテーブルがあるから。

追記 2012-02-15
しゃれにならないプロセスがあったら、まずはshow processlistでIDを補足。多分一番左にあると思われる、数字。
で、その数字に対して

mysqladmin kill {ID}

で、その対象プロセスをkill。
ド最悪、これで「厄介なSQL プロセス」を殺せます。

MySQLのパーティションでセッションテーブル管理:失敗編

とりあえずアバウトにmemo。


やりたいこと。

drop table session_test;
create table session_test (
  session_id varbinary(256) not null,
  use_id     varbinary(64) not null,
  data       blob,
  insert_date datetime,
  update_date datetime,
  PRIMARY KEY (session_id)
) ENGINE=InnoDB
  PARTITION BY RANGE ( hour(update_date) ) (
    PARTITION p00 VALUES less than(0),
    PARTITION p01 VALUES less than(1),
    PARTITION p02 VALUES less than(2),
    PARTITION p03 VALUES less than(3),
    .
    .
    .
    PARTITION p23 VALUES less than(23),
    PARTITION pmax VALUES LESS THAN MAXVALUE  
  );

んで、バッチで

DROP PARTITION p(二時間前);

ってやりたいげ。


で…現実。
まずそもそもとして
「A PRIMARY KEY must include all columns in the table's partitioning function」
って怒られる orz
だって「レンジの値はど〜したってPKぢゃねぇもん;;」

update_dateが15:59:00から16:00:00にupdateされた時って、ちゃんと動くのかしらん?
いやまぁ「PKだからうごかねぇよ!」って前提条件は置いておくとして。
# 一説によると*1これはちゃんと対応してるぽ…それだけになおのこと「PK縛り」が残念にすぎる orz


ちなみに、本気でやると、パーティションの設定部分が多分

    PARTITION p00 VALUES IN (0,3,6, 9,12,15,18,21),
    PARTITION p01 VALUES IN (1,4,7,10,13,16,19,22),
    PARTITION p02 VALUES IN (2,5,8,11,14,17,20,23),
    PARTITION pmax VALUES LESS THAN MAXVALUE  

ってなる。「今必要なパーティション」「念のために寝かせておくパーティション」「dropするパーティション」があれば十分なはずだしね。


さて…こんな風に使える日はくるのか!?

でっかいテーブルをまりっと更新する方法のひとつ の応用編と困ったこと

んで。
これの亜種として「部分的に入れ替える」事を、やることがあります。
んと…住所だと「東京都だけ入れ替える」とか。

trancate 郵便番号テーブル_tmp;
loop insert into 郵便番号テーブル_tmp(...) values(...);
begin;
delete from 郵便番号テーブル where 都道府県='東京都';
insert into 郵便番号テーブル(...) select ... from 郵便番号テーブル_tmp;
commit;

で…まぁ「結構でかい」のをやったら、とっても嫌がられました orz


The total number of locks exceeds the lock table size


えと…「でけぇよ!!」って感じ?
基本的には
my.cnfの中にある「innodb_buffer_pool_size」をでっかくしてあげると良いみたい。


…さて。いくつにしたらよかんべ orz

でっかいテーブルをまりっと更新する方法のひとつ

んと…例えば「郵便番号をkeyにした住所テーブル」なんてのが、割とわかりやすいところであるのですが。
この子を更新する場合、普通に考えると

begin;
trancate 郵便番号テーブル;
loop insert into 郵便番号テーブル(...) values(...);
commit;

ってな処理かと思うのですが(エラー時の処理は省略してるので気をつけてね)。
この場合、トラン中が結構長いのと、そのために、結構なDB負荷がかかります(いやまぁ真面目に計測はしてないんですが)。
そこで。若干荒っぽく、こんな処理の仕方があります。

trancate 郵便番号テーブル_tmp;
loop insert into 郵便番号テーブル_tmp(...) values(...);
begin;
trancate 郵便番号テーブル;
rename table 郵便番号テーブル_tmp to 郵便番号テーブル;
commit;


いやまぁたいした手段でもないのですが「こーゆーやりかたもあるでよ」ってことで。
これだと、極論「insert1文ごとにマイクロスリープ」とかぶち込んだり「DBハンドルの接続数を確認して、一定数以上ならしばらくwait」とか、まぁ細かい小手先技が、色々と盛り込めるもんでw