monoの開発ブログ

MySQL でテスト用データベースを高速に空にする (information_schema.tables の table_rows の話)

全テーブルを TRUNCATE して、必要なデータを流し込んでテストを実行するみたいなのがひたすら繰り返されるコードがあって、とにかく遅くてつらかったので高速化できないか考えてみました。

全テーブル TRUNCATE というのは、要するに一度データベースをまっさらな状態に持って行きたいという話なのですが、巨大なプログラムの1つ1つのテストは一部のテーブルしか使わないことがほとんどであるため、TRUNCATE せずともまっさらな状態のままテストを終えるテーブルは多いわけです。そこで、まずテーブルごとに TRUNCATE が必要かどうか判定して、TRUNCATE が必要と判断したテーブルに対してのみ TRUNCATE を発行するコードを考えてみました。

MySQL だと information_schema.tables に独自に便利な情報が格納されているので、これを使うのが手っ取り早いです。テーブルに行が存在するか、あるいは auto increment の値が進んでいた場合に TRUNCATE が必要と判断するようにしています。

my $dbh = DBI->connect(...);

my @tables = $dbh->fetchall_arrayref('
    SELECT table_name
    FROM information_schema.tables
    WHERE
        table_schema = DATABASE()
        AND (
            table_rows > 0
            OR auto_increment > 1
        )
');
for my $table (@tables) {
    my ($table_name) = @$table;
    $dbh->do("TRUNCATE TABLE $table_name");
}

これが高速かというとまだまだやりようはありますが、ひたすら全部 TRUNCATE だった状態から脱却する第一歩としてはなかなかコストパフォーマンスがよいアプローチだと思っています。

なお、MySQL のドキュメントを見ると、InnoDB において table_rows の値は概算値であり、正確ではないと記されています。そのため、行が存在するかどうかの判定に table_rows を利用しても問題がないか気になると思います。

ソースコードを確認したところ、MySQL においてストレージエンジンの統計情報が格納される ha_statistics 構造体の定義には、行数が0以外の場合にはストレージエンジンのフラグによって概算値になる可能性があるものの、0の場合に限っては厳密に0であることを意味すると記されていました。InnoDB が概算値を出す部分までは追っていませんが、今回のように行が存在するかどうかの判定に行数の概算値を利用することは問題なさそうです。