@blog.justoneplanet.info

日々勉強

CakePHPとMySQLのON UPDATE CURRENT_TIMESTAMPを使う

CakePHPにおいてON UPDATE CURRENT_TIMESTAMPを使用すると、文字列としてCURRENT_TIMESTAMPを格納しようとするので、MySQL側でTIMESTAMPとして処理できない。以下のようにすることで対処できる。

■CURRENT_TIMESTAMP

modifiedというカラム名をつければCakeが自動的に更新日時をセットするので必ずしもON UPDATE CURRENT_TIMESTAMPをセットする必要はない。

■saveメソッド

習慣とは恐ろしい物で付けたい物は付けたいのだ。saveメソッドで保存するカラムを指定することで対処できる。

$this->User->save($this->data, true, array('name', 'birthday'));

上述のようにすることで$this->dataにあらゆるキーとそれに紐付く値が格納されていても、保存されるのはnameカラムとbirthdayカラムのみである。これはアプリケーションをセキュアにする目的でも使用できるので、常時使用することをお勧めする。

mysqliを使ってPHPでMySQLにSSLで接続する

MySQLサーバがクライアントとは別のコンピュータにある場合、ネットワークを平文で流れるデータを途中でキャプチャされる可能性がある。そこでSSL接続することにする。

■ソース

以下のように、リソースを生成後にmysqli_ssl_setでSSLをセットしてから、mysqli_real_connectを使って接続する。

$dbh   = mysqli_init();
$isSSL = @ mysqli_ssl_set(
    $dbh,
    '/home/ssl/key.pem',
    null,
    null,
    null,
    null
);
$isConnected = @ mysqli_real_connect(
    $dbh,
    DB_HOST,
    DB_USER,
    DB_PASS,
    DB_NAME,
    DB_PORT,
    null,
    MYSQLI_CLIENT_SSL
);

MySQLサーバーをインストールして初期設定をする

■インストール

yum install mysql-server

PHPから使う場合は以下のようにphp-mysqlもインストールする。

yum install php-mysql

MySQLにアクセスする。

mysql -u root

■パスワード

アクセスできるのは良いんだがパスワードが設定されてないのはイカン。(・ε・)

SET PASSWORD FOR root@localhost=PASSWORD('fugafuga');

■データベース

ちょっとデータベースを作ってみる。

CREATE DATABASE `sample`;

■テーブル

ちょっとテーブルを作ってみる。

USE `sample`;
CREATE TABLE  `sample`.`tbl1` (
    `id` INT( 11 ) NOT NULL AUTO_INCREMENT ,
    `name` VARCHAR( 255 ) NOT NULL ,
    PRIMARY KEY (  `id` )
) ENGINE = MYISAM ;

■文字コード

ネットで探してもなかなか適切な文献が見当たらない。凄く深いので結論だけまとめておく。

ちょっとPHPからデータを入れてみる。

$dbh = mysql_connect(DB_HOST, DB_USER, DB_PASS);
mysql_select_db(DB_NAME, $dbh);
$query = "INSERT INTO `tbl1`(`name`) VALUES('山田');";
$result = mysql_query($query);

phpMyAdminで見てみると以下のように文字化けする。

capture

MySQLのデフォルト文字コードはlatin1になっている為だ。アプリケーション側で表示するときは特に文字化けすることはないかもしれないが、管理上は非常に面倒なのでデフォルト文字コードをutf-8にする事を強く勧める。

my.cnf(my.ini)

以下のようにする。

[mysqld]
character-set-server=utf8
[client]
default-character-set=utf8

但しMySQLクライアントのコンパイルオプションによっては全然文字化けしまくる。従ってプログラム側での修正も必要だ。

PHP

mysql_connectを使う

mysql_connectを使用している場合は5.2.3以上が必須で、以下のようにクライアント側の文字コードを設定する。

$dbh = mysql_connect(DB_HOST, DB_USER, DB_PASS);
mysql_set_charset('utf8', $dbh);
PDOを使う

PDOを使用している場合は以下のように、MySQLの設定ファイルを読み込み、ATTR_EMULATE_PREPARESをfalseにしてサーバーサイドPrepared Statementを使うようにする。

$dsn = 'mysql:host=' . DB_HOST . ';dbname=' . DB_NAME;
$dbh = new PDO(
    $dsn,
    DB_USER,
    DB_PASS,
    array(
        PDO::MYSQL_ATTR_READ_DEFAULT_FILE => '/etc/my.cnf'
    )
);
$dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

但し、MySQL<5.1の場合はクエリキャッシュが聞かないので注意が必要だ。現時点ではPDOでクライアント側の文字コードを指定する方法は無い。

誤り
  • 「SET NAMES utf8」クエリを発行する。
  • 「skip-character-set-client-handshake」をmy.cnfに記述する。
参考

「PHP5.2.3以前」かつ「PDOを使用できない」かつ「再コンパイルできない」とき文字コードの問題を完全に解決するのは不可能とのこと。

mysqldumpする

■mysqldump

mysqldump --no-autocommit -u root -p dbname > db.dump
scp -r ./db.dump user@123.123.123.123:/home/db.dump

123.123.123.123

cd /home
mysql -u root -p --default-character-set=utf8 < db.dump

50MBくらいなら10秒で終わる。

■バックアップ

以下のようにすることでmysqldumpを利用して定期的にデータベースをbackupできる。

#!/bin/sh
TDAY=`date +%Y%m%d`
cd /var/www/hogehoge.justoneplanet.info/app/vendors/shells
mysqldump --no-autocommit -u root --password=hogehoge dbname > $TDAY.dump

crontab

以下のように記述することで、毎日1:15にバックアップが実行される。

15 1 * * * /var/www/hogehoge.justoneplanet.info/app/vendors/shells/backup.sh

SQLの性能をテストする

以下のようにEXPLAINしてみるとイイ。

EXPLAIN SELECT *
    FROM  `parent`
    LEFT JOIN  `child`
    ON  `parent`.id =  `child`.id_parent
    WHERE  `parent`.id = 1

どうやらchildテーブルを全て検索してるようだ。

table type possible_keys key ref rows
parent const PRIMARY PRIMARY const 1
child ALL NULL NULL NULL 311

■改善

以下を実行してインデックスを付加し、クエリの性能を向上させる。

ALTER TABLE `child`
    ADD CONSTRAINT `fk_child_tbl_parent_tbl`
    FOREIGN KEY (`id_parent`) REFERENCES `parent` (`id`)
    ON DELETE CASCADE
    ON UPDATE NO ACTION;

劇的だ!ヾ(@⌒▽⌒@)ノchildテーブルを1行しか検索しなくなった。

table type possible_keys key ref rows
parent const PRIMARY PRIMARY const 1
child ref fk_child_tbl_parent_tbl fk_child_tbl_parent_tbl const 1

参考

type
結合型。
const。テーブルに、一致するレコードが最大で 1 つ。
ref。前のテーブルのレコードの組み合わせのそれぞれに対して、インデックス値にマッチするすべてのレコードがこのテーブルから読み取られる。ref は、『インデックスの左端の先頭部分のみが結合で使用される場合』、『インデックスが UNIQUE や PRIMARY KEY ではない場合』に使用される。
ALL。前のテーブルのレコードの組み合わせのそれぞれに対して、フルテーブルスキャンが実行される。
possible_keys
テーブル内のレコードの検索に MySQL で使用可能なインデックスを示す。
key
MySQL が実際に使用を決定したキー(インデックス)を示す。
ref
テーブルからレコードを選択する際に key とともに使用されるカラムまたは定数。
rows
クエリの実行に際して調べる必要があると MySQL によって判定されたレコードの数を示す。少ない方がイイ。

複数テーブル同時削除

以下のようにdelete文を使用すると、複数テーブルからレコードが削除できる。

DELETE
    `table`,
    `child`,
    `grandchild`
FROM
    `table`
LEFT JOIN
    `child`
ON
    `table`.id = `child`.`id_table`
LEFT JOIN
    `grandchild`
ON
    `child`.id = `grandchild`.`id_child`
WHERE
    `table`.id = 1

但し、外部キー制約でON DELETE CASCADEを使った方が早い気がする。

外部キー制約

以下のようにする。

ALTER TABLE `child`
    ADD CONSTRAINT `fk_child_table_parent_table`
    FOREIGN KEY (`id_parent`) REFERENCES `parent` (`id`)
    ON DELETE CASCADE
    ON UPDATE NO ACTION;

こうするとparentテーブルからレコードAを削除した時に、レコードAのidとchildテーブルのid_parentが等しいレコードが同時に削除される。

ちなみにid_contentsは全く同じ型でないとALTER TABLEが失敗する。