blog

Java、PHP、C++、MySQL、Apache、Linux、UnixなどのTechを紹介

MySQL

MySQLのUDF(ユーザ定義関数)機能を使い、アルファベットの文字列をひらがな順にソートするオリジナル関数を作成してみた。このUDF関数はアルファベットの文字列データが含まれるMySQLのデータカラムに対して、日本語固有のひらがな順にソートを可能にする。漢字、ひらがなで登録された2byte文字列データであれば、MySQL自身が内部で持っている辞書データによりひらがなソートが可能なのだが、アルファベットの文字列データをソートしようとした場合は英語固有のアルファベット順ソートになってしまう。そこで今回作成した関数(alpha_to_hiragana関数と命名)内部において、アルファベットの文字列に対して、ひらがな順にソートできるように、ひらがな順にデータの重みづけの内部変換を行い、ひらがなソートを実現した。alpha_to_hiragana関数の開発ポイントを今回記述してみたいと思う。

そもそものUDF関数の実装方法を知りたい方は、下記の関連リンク先サイトがわかりやすいのでそちらを見てほしい

アルファベット順ソートとひらがな順ソートの違い

まずは、通常のソート(アルファベット順)とalpha_to_hiragana関数(ひらがな順)を 使ったソートの処理結果の違いを見てもらいたい

無秩序にならんだサンプルデータ(テーブル名:actor)

+-------------------+
| name              |
+-------------------+
| Saitou akira      |(さ行)(頭文字:S)
| L7ucky Isono      |(ら行)(頭文字:L)
| aida masao        |(あ行)(頭文字:A)
| YamadaShougo      |(や行)(頭文字:Y)
| AKAGI haruka      |(あ行)(頭文字:A)
| Mike Davis        |(ま行)(頭文字:M)
| Tanaka123         |(た行)(頭文字:T)
| hamadamayui       |(は行)(頭文字:H)
| goto satoru       |(か行)(頭文字:G)
| WANIDA KENJI      |(わ行)(頭文字:W)
| N-A-G-U-R-A-R-Y-O |(な行)(頭文字:N)
+-------------------+

アルファベットソート後(A->B->C->D->E...順に並びかえられた)

使用したクエリ:SELECT name FROM actor ORDER BY name ASC
+-------------------+
| name              |
+-------------------+
| aida masao        |(頭文字:A)
| AKAGI haruka      |(頭文字:A)
| goto satoru       |(頭文字:G)
| hamadamayui       |(頭文字:H)
| L7ucky Isono      |(頭文字:L)
| Mike Davis        |(頭文字:M)
| N-A-G-U-R-A-R-Y-O |(頭文字:N)
| Saitou akira      |(頭文字:S)
| Tanaka123         |(頭文字:T)
| WANIDA KENJI      |(頭文字:W)
| YamadaShougo      |(頭文字:Y)
+-------------------+

ひらがなソート後(あ->か->さ->た->な...順に並びかえられた)

使用したクエリ:SELECT name FROM actor ORDER BY alpha_to_hiragana(name) ASC
+-------------------+
| name              |
+-------------------+
| aida masao        |(あ行)
| AKAGI haruka      |(あ行)
| goto satoru       |(か行)
| Saitou akira      |(さ行)
| Tanaka123         |(た行)
| N-A-G-U-R-A-R-Y-O |(な行)
| hamadamayui       |(は行)
| Mike Davis        |(ま行)
| YamadaShougo      |(や行)
| L7ucky Isono      |(ら行)
| WANIDA KENJI      |(わ行)
+-------------------+

アルファベット文字列をひらがな順への重みづけ方法

YamadaTarou(やまだたろう)、TanakaMasao(たなかまさお)をサンプルデータとして説明する

  1. 文字列を小文字に変換する

  2. YamadaTarou => yamadatarou
    TanakaMasao => tanakamasao
    
  3. ローマ字読みから重みづけるための変換テーブルを作成する

  4. 以下が変換テーブルである、母音のあ行は1文字全てにアルファベットを割り当て、 子音はまとめて1文字のアルファベットを割りあてる

    あ行:あ(a)、い(b)、 う(c)、 え(d)、 お(e)
    か行:(f)
    さ行:(g)
    た行:(h)
    な行:(i)
    は行:(j)
    ま行:(k)
    や行:(l)
    ら行:(m)
    わ行:(n)
    
  5. 変換テーブルを用いて、サンプルデータを変換する

  6. y a m a d a t a r o u
    ↓
    l a k a h a h a m e c
    
    t a n a k a m a s a o
    ↓
    h a i a f a k a g a e
    
  7. 変換後のアルファベット文字列で比較を行う

  8. y a m a d a t a r o u
    
    [昇順]  h a i a f a k a g a e  <<<  l a k a h a h a m e c  [降順]
              (TanakaMasao)               (YamadaTarou)
         (たなかまさお)       (やまだたろう)
    

    このようにアルファベット文字列であっても、ひらがな順にソートを実現できるようになる。実際のソートに関しては、alpha_to_hiragana関数内で実装するのではなく、MySQLのORDER BY句で利用してひらがなソートを実現している。

興味をもたれた方は

SOURCEFORGE.JPのMySQLibプロジェクト内にUPしているので 下記よりアクセスしていただけると幸いである

  • alpha_to_hiragana関数の詳細な使用方法はこちら
  • alpha_to_hiragana関数のソースコードのダウンロードはこちら
 
投稿日:2009/06/14 | カテゴリ:MySQL | コメント・TrackBack:(0)
MySQL

MySQLでDATETIME型のカラムを追加して、YYYY-MM-DD HH:MM:SSの形式でデータを保存することはよくあるケースだと 思う。DATETIME型のカラムをWHERE以降の条件句に指定して、年月日時でデータの絞込みを行い目的のデータを抽出 するという時に、より高速にデータを取得できないかと考えてみた。

前提条件

  • テストはクエリを10回発行した平均値より比較する
  • テストパターンとして、INDEX有り・無しの2パターンで比較を行う
  • テストデータは100万レコード用意する
  • INT型のデータの作成にはMySQLのUNIX_TIMESTAMP()関数を使う
  • Query CacheはOFFにして行う

今回テストで使用したサーバのスペック

OS :CentOS release 4.5 (Final)
DB :MySQL5.0.77
CPU:Core 2 Duo 1.86GHz 
Mem:1GByte

今回テストで使用したテーブルスキーマ、データ構造

(DATETIME型のデータ構造 テーブル名:test_datetime)

mysql> desc test_datetime;
+-----------+----------+------+-----+---------+----------------+
| Field     | Type     | Null | Key | Default | Extra          |
+-----------+----------+------+-----+---------+----------------+
| id        | int(11)  | NO   | PRI | NULL    | auto_increment |
| datetime  | datetime | NO   | MUL | NULL    |                |
+-----------+----------+------+-----+---------+----------------+
+----+---------------------+
| id | datetime            |
+----+---------------------+
|  1 | 2008-08-23 19:03:26 |
|  2 | 2008-08-23 19:03:26 |
|  3 | 2008-08-23 19:03:26 |
|  4 | 2008-08-23 19:03:26 |
|  5 | 2008-08-23 19:03:26 |
|  6 | 2008-08-23 19:03:26 |
|  7 | 2008-08-23 19:03:26 |
|  8 | 2008-08-23 19:03:26 |
|  9 | 2008-08-23 19:03:26 |
| 10 | 2008-08-23 19:03:26 | 
| ・ |         ・         |
| ・ |         ・         |
| ・ |         ・         |
+----+---------------------+

================================================

(INT型のデータ構造 テーブル名:test_unixtime)

mysql> desc test_unixtime;
+-----------+----------+------+-----+---------+----------------+
| Field     | Type     | Null | Key | Default | Extra          |
+-----------+----------+------+-----+---------+----------------+
| id        | int(11)  | NO   | PRI | NULL    | auto_increment |
| unixtime  | int(10)  | NO   |     | NULL    |                |
+-----------+----------+------+-----+---------+----------------+
+----+------------+
| id | unixtime   |
+----+------------+
|  1 | 1219485806 |
|  2 | 1219485806 |
|  3 | 1219485806 |
|  4 | 1219485806 |
|  5 | 1219485806 |
|  6 | 1219485806 |
|  7 | 1219485806 |
|  8 | 1219485806 |
|  9 | 1219485806 |
| 10 | 1219485806 |
| ・ |     ・     |
| ・ |     ・     |
| ・ |     ・     |
+----+------------+

それではテスト開始・・・


INDEX無しの状態でのベンチマークテスト

比較演算子を使ったクエリ

(DATETIME型の場合)

SELECT * FROM test_datetime WHERE datetime = '2008-08-23 19:10:11';

処理結果(2.99 sec)

(INT型の場合)

SELECT * FROM test_unixtime WHERE unixtime = 1219486211;

処理結果(0.74 sec)

圧倒的にINT型にしたクエリ条件のほうがデータ取得の処理スピードが4倍高速の結果になった。
(※年月日時を=(イコール、等値)を使った検索条件句を使うケースは実際のシステム設計の中ではあまりないようなケースだと思う。実際によく使われるのはやはり、次に紹介したBETWEENを使った期間指定検索であろう)

BETWEENを使った期間指定を使ったクエリ

(DATETIME型の場合)

SELECT * FROM test_datetime WHERE datetime 
              BETWEEN '2008-08-23 19:00:00' AND '2008-08-23 19:05:00';

処理結果(5.20 sec)

(INT型の場合)

SELECT * FROM test_unixtime WHERE unixtime 
              BETWEEN 1219485600 AND 1219485900;

処理結果(1.64 sec)

BETWEENを使った期間指定クエリでもINT型の方がデータ取得の処理スピードが3倍高速の結果である

INDEX有りの状態でのベンチマークテスト

比較演算子を使ったクエリ

(DATETIME型の場合)

SELECT * FROM test_datetime WHERE datetime = '2008-08-23 19:10:11';

処理結果(2.99 sec)

(INT型の場合)

SELECT * FROM test_unixtime WHERE unixtime = 1219486211;

処理結果(0.02 sec)

INT型の方がデータ取得の処理スピードが150倍高速の圧倒的効果である。INT型はINDEXを最適に使い目的の結果を返してくれるためここまでのパフォーマンス結果がでたものと思われる。面白い副産物結果として、DATETIME型ではINDEX有り・無しかかわらず処理結果値が同じということで、DATETIME型はINDEXの恩恵を受ける事があまりできないのである (※ただしINDEX無しの結果内容の所でも記載したが=(イコール、等値)を使った検索条件句を使うケースはあまりないかもしれない・・・)

期間指定を使ったクエリ

(DATETIME型の場合)

SELECT * FROM test_datetime WHERE datetime 
              BETWEEN '2008-08-23 19:00:00' AND '2008-08-23 19:05:00';

処理結果(5.12 sec)

(INT型の場合)

SELECT * FROM test_unixtime WHERE unixtime
              BETWEEN 1219485600 AND 1219485900;

処理結果(1.64 sec)

DATETIME型、INT型もINDEXを設定しても、期間検索というB-tree型のINDEXの恩恵の受けにくい検索条件句であるため、INDEXが無い場合と比較してそこまで処理スピードの向上には影響を及ぼさなかったが、ここでもINT型の方がデータ取得の処理スピードが3倍高速の結果にはなった

結論

DATETIME型で保存するのではなく、UNIX_TIMESTAMP関数を使いINT型にコンバートした形式で保存することにより、DATETIME型より3倍~4倍高速化が実現できた。その際に注意しなければならないのが、INT型の年月日時を指定したカラムを生成しても実際にクエリを生成する際に、'1219485900'(シングルクォート)して文字列(STRING型)として指定しないこと。うっかりこのように指定してしまうと、カラムがINT型であっても処理スピードが遅くなってしまう。ただし、このINT型で年月日時データを保存する方法にもデメリットがあって、UNIX_TIMESTAMPなので直感的に見て人間が理解しずらいのである。そのためサーバのコマンドラインよりMySQL Clientを使いダイレクトにアクセスしデータを取得する必要があるケース(exp. 人間の手による緊急的な集計オペレーション)では、YYYY-MM-DD HH:MM:SSの形式にコンバートする必要があるかと思う。その場合は人手間だが以下のようにFROM_UNIXTIME関数を使用して結果を出力してほしい

SELECT id, FROM_UNIXTIME(unixtime) FROM test_unixtime  
               WHERE unixtime BETWEEN 1219485600 AND 1219485900;



 
投稿日:2009/06/10 | カテゴリ:MySQL | コメント・TrackBack:(2)