皆様、ご無沙汰しております。笹亀です。
いよいよきたる、9/10に新しいiPhone5 S(仮名)が発表される予定ということで、iphone4を使っている自分は今回のタイミングで変更する予定なので、いまから発表が楽しみです。
さて、本日はフレームワークなどを利用している場合などであれば、あまり使用することはないですが、PDOを利用したレプリケーションしたデータベースのコネクションを切り分ける方法をご紹介したいと思います。PHPでも様々なフレームワーク(symfony,Cake,ZendFramwork)を使い開発をされるようになってきており、あまりレプリケーションの切り分けを考える必要がなくなってきておりますが、切り分けを行う方法(考え方)という視点で見ていただけますと幸いです。
※尚、ご使用される場合は自己責任でお願い致します
概要について
レプリケーションのコネクションを切り分けることについては、一つのデータベースサーバであれば特に気にする必要はございません。下記の画像のように、改修後Master・Slaveのレプリケーション形式にスケールアウトしたい場合には更新系の処理(INSERT, UPDATE, DELETEなど)はMaster側にて処理をする必要があります。そしてデータ取得系の処理をSlaveを優先的に使用して、Masterと分散をさせるようにするのが一般的です。
スクリプト(クラスファイル)
コネクションを操作するクラスでは、実際に実行しようとしているSQL文をチェックして、Slaveで実行していいSQL(SELECT)か?Masterでないと実行してはいけないSQL(INSERT, UPDATE, DELETEなど)かを判定して、PDOのコネクションを切り分けてあげるようなクラスにします。
実際に上記の内容をクラス化したPHPスクリプトは下記よりご確認ください。(別ウィンドで立ち上がります)
こちら
それぞれのメソッド解説
上記のプログラムを開いた状態で下記の解説を照らしあわせて下さい。
__construct
1.Masterのコネクションなどの情報を格納する内部変数の初期化
2.MasterとSlaveのPDOコネクションを生成
※PdoDatabaseを生成するときの引数に「master」と渡すと、MasterのPDOのみを利用する機能も設けている
query, exec, prepare, getAssoc...
1.実行するSQLからPDOコネクション情報を取得(selectConnection)
2.PDOオブジェクトにてSQLを実行する
3.結果を返す
※基本的にはPDOオブジェクトで実行できるメソッドと同じ処理をしている。違う点は実行するSQL文によってコネクション情報を選択する部分。
lastInsertId
1.MasterのPDOコネクション情報を取得
2.lastInsertIdメソッドにて実行したIDを取得
3.結果を返す
beginTransaction
1.強制的にMasterへの接続する状態にし、MasterのPDOコネクション情報を取得
2.beginTransactionメソッドを実行
commit, rollBack
1.MasterのPDOコネクション情報を取得
2.commit, rollBackメソッドにて実行
setAttribute
1.MasterとSlaveのPDOコネクション情報を取得
2.それぞれのPDOにsetAttributeメソッドにて属性情報の設定を実行
setMasterMode
※強制的にMasterへの接続する状態に変更するメソッド
getConnection
※現在の実行しているコネクション情報(Master or Slave)を取得するメソッド
getMasterConnection
※MasterのPDOコネクション情報を取得するメソッド
getSlaveConnection
※SlaveのPDOコネクション情報を取得するメソッド
setConnection
※MasterとSlaveのPDOコネクション情報をセットするメソッド
selectConnection
※実行するSQLや設定情報からMasterとSlaveのPDOコネクション情報を選択して、コネクション情報を返すメソッド
isSQLDirty
※SlaveのPDOコネクションで実行してはいけないSQLかチェックするメソッド
isSelect
※SQLがselect文かどうかチェックするメソッド
isMaster
※MasterのPDOにて実行したかどうかチェックするメソッド
使用方法サンプル
実際に正しくMasterとSlaveの切り分けができているかを確認するために、下記のスクリプトで試します。
テスト用のテーブルを作成している前提となります。
CREATE TABLE `test_table` (
`id` int(11) NOT NULL DEFAULT '0',
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
);
<?php
require_once('./pdo_database.php');
$pdo_db = new PdoDatabase();
$sql = 'INSERT INTO test_table values("1", "Sasagame Hiroshi")';
$rec = $pdo_db->exec($sql);
$sql = 'INSERT INTO test_table values("2", "Sasagame Hiroshi")';
$rec = $pdo_db->exec($sql);
var_dump($pdo_db->isMaster());
print_r($rec);
echo '<hr />';
$sql = 'SELECT * FROM test_table';
$sth = $pdo_db->prepare($sql);
$sth->execute();
$rec = $sth->fetchAll();
var_dump($pdo_db->isMaster());
print_r($rec);
echo '<hr />';
$pdo_db = new PdoDatabase();
$sql = 'SELECT * FROM test_table';
$sth = $pdo_db->prepare($sql);
$sth->execute();
$rec = $sth->fetchAll();
var_dump($pdo_db->isMaster());
print_r($rec);
echo '<hr />';
$pdo_db = new PdoDatabase();
$sql = 'DELETE FROM test_table';
$rec = $pdo_db->exec($sql);
var_dump($pdo_db->isMaster());
print_r($rec);
■実行結果
var_dumpがtrueだとMasterで実行
var_dumpがfalseだとslaveで実行
一度、Masterで実行されたPDOコネクションはずっとMaster側を利用するような仕様にしております。なぜなら、Insert文の実行後のlastinsertIDの実行やTransactionが実行された状態のSELECT文の実行の場合は必ずMaster側にて実行する必要があるからです。
実際にレプリケーションのコネクション操作はライブラリなどを利用するのもいいですが、実際にスクリプトを書いてみるとMaster側で実行しないといけないSQL(Transaction処理やTableLock処理)をSlaveのコネクションで実行させてはいけないという点で注意するが多いことがわかります。バグなどで間違えてSlaveで実行してしますとレプリケーションが崩れてしまいます。
実際に書いてみて初めて気付かされる点も多いので、ライブラリを利用する側でもいいですが、実際に便利なライブラリをお手製で作ってみて、実際にコーディングすることで大変さを学ぶのもエンジニアとしてのスキルアップにつながるものではないかと思います。