【まだ途中】SQLite, Redis の Insert 比較 (By PHP)【ツッコミ歓迎】
動機
さまざまなDBの速度比較をしたかった。
各種DBへの個人的印象
比較する前に、どんなふうに思っているかを。。。ちなみに今回、私の比較では、検索とかしていないのでDatabaseってよりはI/Oなのかなと思ったり。RDBMSをそういうふうに使うのは本末転倒...?
SQLite
クライアントサーバー型ではないDB、つまりローカルでしか使えないらしい(とは言えアプリケーションに内蔵すればいい話ですが)がかなり高速らしい。
Redis
インメモリデータベースで、Key-Value系。と言っても単純なKey-Valueではないが。RedisはREmote DIctionary Serverの省略。メモリでやり取りするだけあって、超高速なのでは。ニコニコ動画でも部分的に使われているとのこと。どうやらRedisチームではCluster化に対応したい様子。ちなみにPredisというPHPのRedisツールでは、簡易的なクラスタリング手法のコードがExampleに載っている。試していないけれど。
MySQL【あとでこれも比較】
定番DB。ライセンスが商用OKなのか不明瞭、さらにはOracleに支配されていて将来不穏。と言いつつも未来永劫使える気がするが。。。そこらかしこで使われていてDBと言えばこれ。世界のブログの30%ぐらいはMySQLさんが裏で動いている感。
PostgreSQL 【あとでこれも比較】
定番DB。ライセンスはBSD・MITライクらしく商用利用も安心。日本で人気?
pgpool-II 【あとでこれも比較】
PostgresSQLのクラスタ版。あまり良く知らない。開発は結構盛んのよう。
Firebird 【あとでこれも比較】
日本ではあまり有名でない気がするDB。MySQL, PostgresSQL, Firebird がオープンソースRDBMS御三家。正直なところ私もよく知らない。
HBase 【あとでこれも比較】
HadoopベースのDB?よく知らない。
MongoDB 【あとでこれも比較】
Key-Value系では一番有名?大人気感。
比較方法
Key-Valueっぽいデータを何万件かinsert or setしてそれにかかった時間を比較。Key-Valueっぽいデータの例↓
key | value |
---|---|
id:1 | 1 |
id:2 | 2 |
id:3 | 3 |
マシン環境
- MacBook Pro Mid 2009
- Mountain Lion (10.8.2)
- 2.53 GHz Intel Core 2 Duo
- 4 GB 1067 MHz DDR3
- インテル® Solid-State Drive 330 120GB
DB環境
すべてローカルで完結するように行った。1台のマシンで完結する環境である。クライアントサーバー型ではなくローカルである。
- SQLite3 (3.7.14.1)
- Redis (2.6.7)
結果
処理時間
件数: 1万件
DB名 | 条件 | 時間(sec) |
---|---|---|
PHP | PHPのarray配列に入れてみた | 0.035 |
Redis | Predis使った | 1.325 |
SQLite3 | 毎回のinsertでtransaction。つまりtransaction1万件 | 10.252 |
SQLite3 | 1万件のinsertを1度のtransactionで済ませた | 0.143 |
総評
上記の結果より、コードの中で配列で持つのが最強!を示した( ー`дー´)キリッ
とは言えそれではやりにくいので、DBを使うわけであるが。
Redisはやはり高速。おそらく内部的にunix socketを使ってのこの結果なわけだが、それにもかかわらず1万IOPS近く出ているのは優秀と言えるだろう。もしかするとほとんどunix socketの処理に時間が取られているのかもしれないとすら感じる。
SQLiteは、毎回トランザクションが掛かるとそれなりに時間が掛かっていた。SSDでもこの程度掛かるわけか。一度のトランザクションで済ませるとRedisを上回る早さを見せるが、一回の書き込みで1万件行うわけでそりゃ高速である。Webサービスからのアクセスを考えると、1万件一気にどうのこうのというイベントはあまり発生しないので、参考にはならないだろう。バッチ処理的な場面では有効かもしれない。
ソースコード
(注意点: SQLiteの処理で、「$pdo->query("drop table test1");」とあるが、↓のコードの初回実行時にはこれをコメントアウトする必要がある。2回目以降は、この処理を書くと良い。)
<?php //*** Redis に set **** require_once('./../Predis/Autoloader.php'); Predis\Autoloader::register(); $client = new Predis\Client(array( 'scheme' => 'tcp', 'host' => 'localhost', 'port' => 6379, )); try { $client->connect(); } catch (Exception $e) { print $e->getMessage(); die("\nRedis/(^o^)\"); } $client->flushall(); $num = 10000; $start = microtime(true); for ($i = 0; $i < $num; $i++) { $str_i = strval($i); $client->set("id:" . $str_i, $str_i); } $end = microtime(true); printf("%.3f sec <- Redis の %s 件の Set\n", (double)($end - $start), $num); //*** SQLite3 に set (毎回の insert ごとに transaction版。こちらが実際に近い?)**** $pdo = new PDO('sqlite:./db.sqlite3', "", ""); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $pdo->query("drop table test1"); $pdo->query("create table test1 (key varchar(32), value varchar(32))"); $stmt = $pdo->prepare("insert into test1 values (:key, :value)"); $start = microtime(true); for ($i = 0; $i < $num; $i++) { $str_i = strval($i); $key = "id:" . $str_i; $pdo->beginTransaction(); $stmt->bindParam(":key", $key); $stmt->bindParam(":value", $str_i); $stmt->execute(); $pdo->commit(); } $end = microtime(true); printf("%.3f sec <- SQLite3 の %s 件の Insert(毎回の insert で transaction)\n",(double)($end - $start),  $num); //*** SQLite3 に set (すべてのinsertを1回のtransactionでこなす。こちらは非現実的?)**** $pdo = new PDO('sqlite:./db.sqlite3', "", ""); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $pdo->query("drop table test2"); $pdo->query("create table test2 (key varchar(32), value varchar(32))"); $stmt = $pdo->prepare("insert into test2 values (:key, :value)"); $start = microtime(true); $pdo->beginTransaction(); for ($i = 0; $i < $num; $i++) { $str_i = strval($i); $key = "id:" . $str_i; $stmt->bindParam(":key", $key); $stmt->bindParam(":value", $str_i); $stmt->execute(); } $pdo->commit(); $end = microtime(true); printf("%.3f sec <- SQLite3 の %s 件の Insert(すべてのinsertをまとめて1回のtransaction)\n",(double)($end - $start),  $num); // *** PHP内での配列生成 $php_array = array(); $start = microtime(true); for ($i = 0; $i < $num; $i++) { $str_i = strval($i); $php_array[] = array("id:" . $str_i => $str_i); } $end = microtime(true); printf("%.3f sec <- PHP内 の %s 件の Array Set\n", (double)($end - $start), $num); // var_dump($php_array); //MySQL版 $mysql = "mysql:host=192.168.0.150;port=3306;dbname=test"; $user = "test_user"; $pass = "test_user"; $pdo = new PDO($mysql, $user, $pass); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); //$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, true); //trueだとSQL文が1回で済む(そのぶんセキュリティが... -> と思ったが1万件で0.5sec遅くなった/(^o^)\ $pdo->query("drop table test_mysql"); // MySQL では、「key」という項目名でエラーなので、key1という名前にした↓ $pdo->query("create table test_mysql (key1 varchar(32), value1 varchar(32))"); $stmt = $pdo->prepare("insert into test_mysql values (:key1, :value1)"); $start = microtime(true); for ($i = 0; $i < $num; $i++) { $str_i = strval($i); $key = "id:" . $str_i; $pdo->beginTransaction(); $stmt->bindParam(":key1", $key); $stmt->bindParam(":value1", $str_i); $stmt->execute(); $pdo->commit(); } $end = microtime(true); printf("%.3f sec <- MySQL(Remote) の %s 件の Insert(1件のinsertごとにtransaction)\n",(double)($end - $start), $num); ?>
生のoutput
(sandbox.phpという名前にしている)
$ php sandbox.php 1.325 sec <- Redis の 10000 件の Set 10.252 sec <- SQLite3 の 10000 件の Insert(毎回の insert で transaction) 0.143 sec <- SQLite3 の 10000 件の Insert(すべてのinsertをまとめて1回のtransaction) 0.035 sec <- PHP内 の 10000 件の Array Set