MySQLの全文表示

2011.07.18 16:54
 MySQLではDBに接続して以下のようにしてPHPで表示できるんですが、数万件のDBを扱ってみたところ、殆ど機能しない(読み込みに時間かかり過ぎ)なので、これはほんとにタダのサンプルですね。
 MySQLの大量データをどうやって扱うのかっていう情報があんまりネット上に落ちてないので、ここでちょっと触れてみます。(全然突っ込んでいません。)
$db = mysql_connect ($db_host,$db_user,$db_passwd);
mysql_query("set names utf8");
mysql_select_db($database_name);

$sql = "select * from $table_name";
$rock = mysql_query("lock tables $table_name READ");
$result = mysql_query($sql);

while ($row = mysql_fetch_array($result)) {
    foreach ($row as $k => $v) {
        print "$k=>$v<br />\n";
    }
}
$unrock = mysql_query("unlock tables");
 数万件のかなり重いデータを扱う場合には、
while ($row = mysql_fetch_array($result)) {
      foreach ($row as $k => $v) {
        print "$k=>$v<br />\n";
      }
}
の部分の扱いが肝になってきます。SQL文実行後の$resultには全データの詳細が格納されてはいるのですが、どういうアルゴリズムになっているのかわかりませんが、CPUに負荷をかけるような処理の重さにはならないんですね。ここまででは数十万件のデータでも難なく処理してしまうというSQLの凄さ。 しかし、
$row = mysql_fetch_array($result)
の処理をすると数万件までは大丈夫ですが、数十万件、数億件...となると結構負荷がかかってきます。しかし、数十万件を一挙に表示して一件づつ内容を読むなんてことは殆どしないので、数十件から数百件ぐらいの単位で分割してページ送りにするというようなことが通常の動作、と考えると、whileで一気に表示するなんてことは結構ナンセンスなわけです。なので、ページ送りのアルゴリズムに送り込んで表示させないと数万件は扱えないということになります。 いずれにしろ、SQL文実行後はページ送りのアルゴリズムを作って、そこで自分で何らかのポンターを作って指定範囲を表示させればよいということになります。

 PHPMyAdminなんかもそうですが、全文表示っていうのはさすがにやっていなくて、全文表示であるにも関わらずページ分割をしているんですね。これ、MySQLの最大の特徴とも言えるんですが、いわゆる整数のカウントみたいなものはものすごい速さで処理するんです。0.0004秒とかいう世界です。しかし各レコードの文字(特にtextなんかの長文)みたいなものを扱うと普通のスクリプト並に遅くなるんです。なので、ページ分割をして数十レコードの処理を一回分の処理にしてあげるわけです。
 で、数万件、数十万件のレコードを処理できるコードを書いてみたいんですが、複雑すぎてもう解説にならんので勘弁してください。肝は、
$result = mysql_query("select count(*) as cnt from $this->default_table_name");
$record_count = mysql_fetch_array($result);
でもって、全レコード数をカウントしてやることです。このコードは対したメモリがなくても数万件のレコードでも難なくカウントしてしまいます。

数万件のレコードを扱うコードサンプル

 設定がかなり面倒なのでオブジェクト使っています。オブジェクトがわからないと殆ど理解不能。
[index.php]
<?php
include("./class_db_core_system.php");

$object_db = new db_core_system();

# DB connect information
$object_db->default_db_host = 'localhost';
$object_db->default_db_user = 'sample_db';
$object_db->default_db_password = 'password';
$object_db->default_db_name = 'sample_db_name';
$object_db->default_charset = 'utf8';

# Table select information
$object_db->default_table_name = 'sample_table';
$object_db->page_renge = '10';# display renge for listing on one page.

$return_db = $object_db->db_connect_core();
if ($return_db) {   
    $sql = "select * from $object_db->default_table_name";
    $object_db->page_number = $_GET['page_number'];
    $object_db->table_core($sql);
}
?>

[class_db_core_system.php]
<?php
class db_core_system {
    public $class_name = 'db_core_system';
   
    # DB connect information.
    public $default_db_host;
    public $default_db_user;
    public $default_db_password;
    public $default_db_name;
    public $default_charset;
   
    # DB table information.
    public $default_table_name;
    public $page_renge; # How many record do you want to list on one page.
   
    public $page_structure = array();
    public $page_number=0 ;
       
    function db_connect_core() {
        $file = __FILE__;
        $method_name =get_class_methods("$this->class_name");
       
        $db = mysql_connect (
            $this->default_db_host,
            $this->default_db_user,
            $this->default_db_password
        )
        or die("Can not connect to db name '$this->default_db_name' on class '$this->class_name', function name '$method_name[0]' in $file.");
       
        $re_charset = mysql_query("set names $this->default_charset");
        $re_select_db = mysql_select_db("$this->default_db_name");
       
        if ($db && $re_charset && $re_select_db) {
            return $db; # return resource DATA.
        } else {
            return 0;
        }
    }

    function table_core($sql) {
        $file = __FILE__;
        $method_name =get_class_methods("$this->class_name");
       
        if ($sql) {
            $rock = mysql_query("lock tables $this->default_table_name READ");
           
            # get all record count.
            $result = mysql_query("select count(*) as cnt from $this->default_table_name");
            $record_count = mysql_fetch_array($result);
            //print $record_count['cnt']."<br />";
           
            # get page count.
            $page_count = ceil($record_count['cnt'] / $this->page_renge);
            //print $record_count['cnt'] / $this->page_renge."<br />";
            //print $page_count."<br />";
           
            # set page structure of record as URL links.
            $page_structure = array();
           
            print $this->page_number."<br />";
           
           
            # page display link as hash-array ($this->page_structure).
            for ($i=0; $i<=$page_count; $i++) {
                array_push($this->page_structure, "<a href='?page_number=$i'>$i</a> | ");
               
            }
            foreach ($this->page_structure as $k=>$v) {
                print "$v";
            }
           
            $result = mysql_query($sql);
           
            $list_start_number = ($this->page_renge*$this->page_number);
            $list_end_number = ($this->page_renge*$this->page_number)+($this->page_renge-1);
            print "<b>$list_start_number</b>-\n";
            print "<b>$list_end_number</b><br />\n";
            $i=0;
            while ($row = mysql_fetch_array($result)) {
                if ($i >= $list_start_number && $i <= $list_end_number) {
                    foreach ($row as $k => $v) {
                        print "$k=>$v |";
                    }
                    print "<br />\n";
                }
                 $i++;
            }
            $unrock = mysql_query("unlock tables");           
        }
    }
}
?>
 数万件、数十万件単位の大容量のデータをMySQLにインポートするのに一番楽で便利なのが、PHPMyAdminを使ってSQLで取り込む方法です。レンタルサーバーなどでデータベースに制約がある場合には、ダメなんですが、PHPMyAdminの設定ファイルをちょっといじってやると簡単にインポートできます。通常、重いファイルは弾かれてしまうみたいです。
 解決方法としては、
./PHPMyAdmin/config.inc.php
なる設定ファイルを開き、一番下の方にある
$cfg['UploadDir'] = '';
という空になっている変数に、
$cfg['UploadDir'] = './uploaddir';
みたいな感じでディレクトリパスを設定してやります。このconfig.inc.phpファイルに対して相対パスで書きます。次に、その指定ディレクトリを作成して、FTPかなんかでそのディレクトリにsqlでエクスポートしたファイルをアップロードしてやります。要するにこのディレクトリから直接インポートファイルを読み込んでくれるわけです。
 PHPMyAdminからインポートの操作を通常時と同様に行うと、「ウェブサーバー上のアップロードディレクトリ」という新しいプルダウンの項目が出来ているので、インポートするファイルをそこから選びます。
 sqlで出力したファイルはそれはそれでいいのですが、不思議なことにCSVファイルも読み込んではくれるものの拡張子を.csvではなく.sqlにしないといけないみたいです。しかしsqlでエクスポートしたものをsqlでインポートした方が安定しているといえば安定しています。
 あまりにも大量データだと途中でタイムアウトしてしまいますが、実行し続けると途中から行ってくれるので全部挿入できます。






プロフィール



  • Name :: 山上オサム ♂(39)
  • Hobby :: 武術
  • Work :: Web Designer