Doctrine2

【Mysql/Doctrine2】where句の記述順によるパフォーマンスの違い。

select時のそういった最適化は全てオプティマイザーが最適解を求めてくれているのかと思い込んでいたがどうやらそういうわけでもない模様。

下記に示す二つのDQLで大きくパフォーマンスが違う。

■パターン1

    select
        p
    from
        HogeFugaBundle:Post as p
    where
        p.body like :str
    and
        p.deletedAt is null
    and
        p.createdAt >= :date

■パターン2

    select
        p
    from
        HogeFugaBundle:Post as p
    where
        p.createdAt >= :date
    and
        p.deletedAt is null
    and
        p.body like :str

だいたいレコード25万くらいのテーブルで実験したところ、パターン1が30秒前後、パターン2が2~3秒といった結果になった。
ある意味素直に最初に指定した条件から順番に絞り込むってことだね。そりゃ25万件のlikeは重いわ。

っていうお話。

 

【Doctrine2】のメモリリークを回避する。

ループ処理などで大量のエンティティをpersist()させて最後にflush()みたいなことをするとメモリをバカ食いするのであまりよろしくない。
下記のようにclear()メソッドを実行してやるとEntityManagerの消費メモリが解放されるので定期的にこの処理を挟むべき。

$em->persist($entity);
$em->clear();

それでもメモリの使用量が思ったように下がってくれない場合はEntityManagerのSQLロガーが原因なので、ログの出力をオフにしてやるとよい。

$em->getConnection()->getConfiguration()->setSQLLogger(null);

これでかなりバッチのメモリ消費量が軽減された。

 

【Symfony2.3】DoctrineMigrationsBundle自分用まとめ。

■インストール

composer.jsonに下記を追加。

{
    ...
    "require": {
        ...
        "doctrine/doctrine-migrations-bundle": "dev-master"
        ...
    }
    ...
}

そしてアップデート。

composer update

app/AppKernel.phpを編集。

...
	$bundles = array(
		...
		new Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle(),
		...
	);
...

app/console コマンドでdoctrine:migrations:…のコマンド群が確認できれば成功。

■Migrationsの初期化

php app/console doctrine:migrations:status

これで初期化完了。データベース「migration_versions」というマイグレーションのバージョン管理用テーブルが作成される。

■マイグレーションファイルを作成する

エンティティの追加、削除、また変更などがあった際、スキーマアップデートを実行してはいけない。
必ずマイグレーションファイルを生成してからマイグレートを実行するようにする。

具体的にはこう。

例)
何かしらのエンティティを変更、追加、削除した際。

# これで最新のマイグレーション履歴との差分を自動的に計算してマイグレーションファイルを生成してくれる。
php app/console doctrine:migrations:diff

マイグレーションファイルは「app/DoctrineMigrations」以下に「Version[実行日時].php」といった形で生成される。
生成されたマイグレーションファイルのup()メソッドとdown()メソッドを確認して、必要であれば編集する。(基本的にそのまま)

問題がなければ下記コマンドでマイグレーションを実行。

php app/console doctrine:migrations:migrate

これで差分のSQLが実行されDBが最新の状態になる。

マイグレーションの差し戻し(down)は過去の別エントリを参照されたし。

 

【Symfony2.3/Doctrine2】で生SQLを実行する。

DQLではMySQLのDATE_FORMATが使えないので生クエリを発行してみた。

例えばブログなどの投稿記事を男女別、日別でカウントを取りたいとすると
雰囲気下記のような感じになる。

// ファイルの先頭で宣言
use Hoge\FugaBundle\Entity\Post;
use Doctrine\ORM\Query\ResultSetMapping;

...
// 関数内
$em = $this->getDoctrine()->getManager();
// 何日前の投稿分まで集計するか
$pastRange = date('Y-m-d', strtotime('-16 days'));

$sql = "select date_format(p.createdAt, '%Y-%m-%d') as date, p.sex as sex, count(p.id) as cnt from posts as p where p.deletedAt is null and p.status in (1, 2) and p.createdAt >= ? 

group by date, sex";

$rsm = new ResultSetMapping();
$rsm->addEntityResult('HogeFugaBundle:Post', 'p');
$rsm->addScalarResult('date', 'date');
$rsm->addScalarResult('sex', 'sex');
$rsm->addScalarResult('cnt', 'cnt');

$query = $em->createNativeQuery($sql, $rsm);
$query->setParameter(1, $pastRange);

// FOSRestBundleを用いてjsonでレスポンスを返却する。
$view = $this->view($query->getResult(), 200)->setFormat('json');
return $this->handleView($view);

これでDQLに無い関数も利用出来るようになった。

おまけ。曜日、時間帯、男女別ごとにカウントを取るSQL。

$sql = "select date_format(p.createdAt, '%w') as day, date_format(p.createdAt, '%H') as hour, p.sex as sex, count(p.id) as cnt from posts as p where p.deletedAt is null and p.status 

in (1, 2) and p.createdAt >= ? group by day, hour, sex";

$rsm = new ResultSetMapping();
$rsm->addEntityResult('HogeFugaBundle:Post', 'p');
$rsm->addScalarResult('day', 'day');
$rsm->addScalarResult('hour', 'hour');
$rsm->addScalarResult('sex', 'sex');
$rsm->addScalarResult('cnt', 'cnt');

うん。楽しい。

createNativeQuery()の詳細は下記公式を参照されたし。

http://doctrine-orm.readthedocs.org/en/latest/reference/native-sql.html

 

【Symfony2.3】doctrine2のfindBy()でlimit、offsetを指定する。

メモ。

findBy()は第3引数が$maxResults、第4引数が$firstResultになっているので下記のように指定すればOK。
※ちなみに第2引数はオーダー。

$em->getRepository('HogeFugaBundle:Piyo')->findBy([
    'status' => 2
], [
    'createdAt' => 'asc'
], 20, 0);

こんな感じ。

 

【Symfony2/Doctrine2】Many-to-Manyのテーブルでリレーションテーブル内をカウントしたい場合。

例えば、記事とカテゴリがあって、カテゴリの数が多い順でカウント、ソートしたい場合のDQLは下記の通りになる。

$dql = "select
            c.id,
            c.name,
            count(c.id) as cnt
        from
            HogeFugaBundle:Categories as c
        join
            c.articles as a
        where
            c.status = 1
        group by
            c.id
        order by
            cnt desc
";

DQLでのjoinのやり方は面白いね。

 

【Symfony2.3】双方向Many-To-Manyの公式リファレンス。

参照する機会が多いので自分用にペタリ。

http://docs.doctrine-project.org/en/2.0.x/reference/association-mapping.html#many-to-many-bidirectional

 

【Symfony2.3】Doctrine2のリレーションに関するメモ。

リレーションに関する解説としてとても参考になるページをみつけたのでペタリ。

http://www.exgear.jp/blog/2010/10/doctrine_association/

なかなか日本語の文献が少ないから、初学者にとってはありたがい。