MySQLをスキーマレスDBとして扱うための試行錯誤のログ1

MySQLのデフォルトのデータベースエンジンはビックデータの格納には向かない。特にALTERでカラムが頻繁に変更になる場合には、行数が多くなるほどALTERに時間が掛かる。なので、そのための用途として、DWHを使うのが一般的です。もしくはSpider+VPとかInfiniDBとか。

以下は、MySQL単体のデータ構造だけで、なんとかしようと足掻いた後のメモ書きです。

例えばこういったテーブルを

user_id repeat category ext_val ext_str ....
1455 4 apple
784 8 orange 5 option
3151 6 apple 7

で以下のような縦もちのデータで定義しておき
type=1がrepeat
type=2がcategory
type=3がext_val
type=4がext_str

user_id type value
1455 1 4
1455 2 apple
784 1 8
784 2 orange
784 3 5
784 4 option
3151 1 6
3151 2 apple
3151 3 7

viewで横もちのデータに戻そうという作戦を考えました。
こうすれば、カラムが追加されてもviewを変更するだけで作業は完了です。かつカラムにデータがない場合に無駄なデータを格納する必要がなくなります。

CREATE TABLE `schema_less` (
 `user_id`   int(11)      NOT NULL,
 `column_id` int(11)      NOT NULL,
 `value`     varchar(255) NOT NULL,
 PRIMARY KEY    (`user_id`,`column_id`),
 KEY `covering` (`user_id`,`column_id`,`value`)
) ENGINE=InnoDB DEFAULT
INSERT INTO schema_less(user_id,column_id,value) VALUES 
( 1455        ,1     ,4        ), 
( 1455        ,2     ,'apple'  ), 
( 784         ,1     ,8        ), 
( 784         ,2     ,'orange' ), 
( 784         ,3     ,5        ), 
( 784         ,4     ,'option' ), 
( 3151        ,1     ,6        ), 
( 3151        ,2     ,'apple'  ), 
( 3151        ,3     ,7        )

ただ単にデータが表示されるようにするだけなら

SELECT 
  user_id,
  sum(           (column_id=1)*value    )              `repeat`  ,
  group_concat(IF(column_id=2 ,value,'') SEPARATOR '') `category`,
  sum(           (column_id=3)*value    )              `ext_val` ,
  group_concat(IF(column_id=4 ,value,'') SEPARATOR '') `ext_str`  
FROM schema_less 
GROUP BY user_id

group byでちゃちゃっと縦横変換を行いこれを元にviewを作るだけ

CREATE VIEW v_user AS 
SELECT 
  user_id,
  sum(           (column_id=1)*value    )              `repeat`  ,
  group_concat(IF(column_id=2 ,value,'') SEPARATOR '') `category`,
  sum(           (column_id=3)*value    )              `ext_val` ,
  group_concat(IF(column_id=4 ,value,'') SEPARATOR '') `ext_str`  
FROM schema_less 
GROUP BY user_id

ところが、このままviewにするとインデックスが効かなくなるという問題が....。

  • ユーザーIDで条件指定した場合
EXPLAIN SELECT * 
FROM  `v_user` 
WHERE  `user_id` =784
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY ALL NULL NULL NULL NULL 3 Using where
2 DERIVED schema_less index NULL PRIMARY 8 NULL 9
explain SELECT 
  user_id,
  sum(           (column_id=1)*value    )              `repeat`  ,
  group_concat(IF(column_id=2 ,value,'') SEPARATOR '') `category`,
  sum(           (column_id=3)*value    )              `ext_val` ,
  group_concat(IF(column_id=4 ,value,'') SEPARATOR '') `ext_str`  
FROM schema_less 
WHERE `user_id` =784 
GROUP BY user_id
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE schema_less ref PRIMARY,covering covering 4 const 4 Using index

どうやらviewにした場合user_idの条件をhavingにしてしまったようです。
いずれにしても、他のカラムの検索はhavingになってしまうので、これではインデックスが効かず実用的ではないですね。

データ構造上は、インデックスを効かせた上で上記実装は可能な筈なので、次回は別の集計手段を考えて見ましょう。
え、GROUP BY以外に集計手段があるの?いや、実はあるんですよ。