Hatena::ブログ(Diary)

SQLer 生島勘富 の日記 このページをアンテナに追加 RSSフィード Twitter

2011-10-08

RDBMSをタバコ屋さんで説明

 勉強会に参加さてもらい、次の勉強会でお話しすることになったので宣伝です。

 前の勉強会では以下の様な実験が話題になっていました。

http://www.doaplus.com/html/bun/bun03_20051101.html

 まあ、極めて当たり前の結果です。

 5000万件が500万件になるというのは、データを横に非正規化すると明細テーブルが要らなくなるから件数が少なくてすむという意味らしい(爆笑)。

 非正規化したテーブルに、更に足りない情報をJOINして得ようとすると、効率的なSQLを書くことが困難になって、非常に実行速度が遅くなる。

 この結果から「なんでこの項目を非正規化しておかなかったんだ!」っていう連中が出て、テーブル構造はどんどんCOBOLのファイルレイアウトに近づいて行く。

 要するに、JOINしたら遅くなると感じるのはテーブル構造が間違っているためですが、その間違った構造を信じているため『正規化してJOINが増えるともっと遅くなる』と感じて、正規化に猛烈な反発をする。現実問題、実際に体感的した結果だから、この感覚を取り除くのは非常に難しいわけです。

 こんなことを真面目に実験してみないと分からないというのは、RDBMSの基本が全く分かってない人があまりにも多いからだろうな……。もちろん、実際に実験された方は分かってやっているのだろうけれどね。


まずは、コンピュータの処理の仕方から

 ごく当たり前の話だけれど、私は何でもイメージで捉えるので、昔に何かの雑誌で見たタバコ屋さんのイメージで考えてみましょう。

 タバコ屋さんにおばあさんが店番として座っています。

 目の前には作業用の小さな机があり、ちょっとした作業は机の上で行います。

 おばあさんは注文があれば、棚からタバコを取って渡します。

 もし、注文されたタバコが棚にない場合は、裏の倉庫から取ってきます。

 裏の倉庫にもない場合はJTに注文を出します。

 これらの処理をコンピュータに置き換えると、

    おばあさん:CPU

    机:二次キャッシュ

    棚:メモリー

    裏の倉庫:ハードディスク

    JT:クラウド上のデータ

 となります。

 棚が小さいまま、おばあさんを若いべっぴんさんに代えても、ほとんどの場合、処理速度は上がりません。まあ、売上は上がるかも知れないけどね(笑)。


ベースは同じなのでOracleで説明

 COBOLerが大挙してこちら側に来た頃「テーブルのことを『ファイル』と呼んでしまう人は、根本的に分かってないから警戒した方がいい」とよく話していたけれど、未だに状況は変わってないのだと思う。

 RDBMSはシーケンシャル(連続的)にデータを保存していません。

    物理的には、ディスク → ファイル → セグメント → エクステント → ブロック。

    論理的には、DB → テーブルスペース(表領域) → テーブル(表) → レコード(行)。

 COBOLなどでは「ファイル=データの集まり」となり、直感的に分かりやすくなるのですが、RDBMSでいうテーブル(表)とは物理的には存在しません。論理的な存在でしかないのです。RDBMSでは論理的な保存先と、物理的な保存先は、ある程度対応はしていても直接は繋がりません。

 物理的な最小単位はブロックになり(SQLServerではページと呼ぶ)ブロックはイメージ的にはルーズリーフのノートになります。SQLServerのようにページと呼ぶ方がしっくりくるのですが、それはさておき、下の図の様にヘッダと空き領域をある程度設定した形になっています。

 http://otndnld.oracle.co.jp/document/products/oracle10g/102/doc_cd/server.102/B19215-02/logical.htm#51885

 データはエクステンドと呼ばれるバインダーに止められて、セグメントという棚に収められています。ファイルはその棚が集まった書庫のような存在で、ディスクは書庫を納めている倉庫というイメージになります。(絵心があれば絵にするんだけどな〜)データが変更されて予備領域を超える追記が必要になったとき、「行移行」という現象が起きますが、ルーズリーフのノートで追記するのに(続きは何ページ)と書いて続けるのと同じです。その行移行を防ぐために固定長のテーブルという笑い話もありましたけどね。

 さて、タバコ屋のおばあさんの話をしましたが、タバコ屋では顧客からの要求がマイルドセブン1個であったとしても、倉庫から持ってくる単位は、カートン(10個入り)が最小単位です。

 RDBMSも同じでレコード単位ではアクセスしません。タバコ屋でいう、カートン単位、ルーズリーフのノート1枚単位にアクセスします。つまり、必要なのは1レコードだけであっても、1枚のルーズリーフのノートに50件のレコードが保存されていれば、50件分をバインダーから外して棚(メモリー)に置いてから処理します。


 COBOLチックに考えると、受注ヘッダテーブルに総アクセスを行えば、素直に入力順にシーケンシャルにアクセスすると考えるかも知れませんが、実際にはバラバラに保存されていて対象のすべてのブロックを読み取っていきます。単純なSQLで処理しようがしまいが、元々バラバラにアクセスするために「求める答えが同じなら、サーバの負荷に変化はない」わけです。単純なSQLで処理すれば「SQLを受取り解析する」という負荷が増えるだけです。

 タバコ屋のおばあさんを想像すれば分かります。タバコを50個欲しいとき、1回の注文にした方がおばあさんが楽か、50回の注文にしたらおばあさんが楽になるか?

 議論の余地はないほど単純な話でしょう。

パフォーマンスは何カートンに触ったか。

 パフォーマンスはおばあさんが何カートンに触ったかと、注文回数(SQLを受け取った回数)でほぼ決まります。

 例えば、顧客商品毎売上が商品毎の売上に占める割合を計算するとしましょう。

 ■サブクエリーで計算

 SELECT

   h.顧客ID, m.商品ID

   , SUM(m.単価 * m.数量) AS 売上合計

   , SUM(m.単価 * m.数量) / s.全体合計 AS 売上割合

 FROM

   売上ヘッダ h

   INNER JOIN 売上明細 m

     ON h.売上ID = m.売上ID

   INNER JOIN

   (SELECT 商品ID, SUM(単価 * 数量) AS 売上合計

   FROM 売上明細

   GROUP BY 商品ID) s

     ON m.商品ID = s.商品ID

 GROUP BY

   h.顧客ID, m.商品ID


 ■OLAP関数で計算

 SELECT

   h.顧客ID, m.商品ID

   , SUM(m.単価 * m.数量) AS 売上合計

   , SUM(m.単価 * m.数量)

     / SUM(m.単価 * m.数量) OVER(PARTITION BY m.商品ID) AS 売上割合

 FROM

   売上ヘッダ h

   INNER JOIN 売上明細 m

     ON h.売上ID = m.売上ID

 GROUP BY

   h.顧客ID, m.商品ID

 実行計画を取らなくても、サブクエリーを使って計算すれば、FROM句のサブクエリーは独立して先に実行されるので、売上明細の全件に2回アクセスしてしまう。サブクエリーの結果はHASH JOINになるはずで、

 

    ■ 売上明細の全件に2回アクセス + HASH JOIN

 

 がサブクエリーのパフォーマンス。

 OLAP関数で処理した場合は、OLAP関数内でソートしてないので四則演算が増えただけ(誤差)。

 つまり、

 

    ■ 売上明細の全件にアクセス + 集計結果に1回アクセス(ソートより速い)

         + 集計結果を商品IDでソート

 

 がOLAP関数で処理したときのパフォーマンス。

 おばあさんが作業するのを想像する。一旦、全部のカートンを数えるために棚に並べ、もう一度、個別の銘柄の数を数えているイメージと、個別の銘柄の数を数えながら、全数を計算し、最後に割合だけを計算し直すイメージができればいい。

 要は、不要なカートンに何度も触ってないか、より一筆書きに近い処理になっているかで処理速度は決まる

 こんなことは、保存方法がシーケンシャルファイルか、RDBMSや、KVSのような形になっているかの違いはあっても、SQLだろうが、OO言語だろうが、COBOLだろうが、おばあさんがやろうが変わらない。私はイメージから入るので、私に取ってはどんな言語を使っても、全部同じ。違うと感じる人は文法から入っているのだろうけれど、文法から入ってパフォーマンスチューニングまで到達するのは、大変な努力だと思うし、本当の意味でパフォーマンスチューニングはできてないと思う。

 文法から入るからでけへんねんって……。

 

 余談ですが、OLAP関数が使えないと2回アクセスしてしまう非効率さは実に悩ましい。しかし、顧客単位の合計までをSQLで出して、割合を別の言語で計算するとか、私の美的感覚ではあり得ない。言語でスパゲッティにしてどうするのか。SQLを2回発行してJOINを別の言語でやるのも、単に非効率なだけであり得ない。

 もちろん、全明細を転送してもほぼデータベースサーバの負荷は変わらないどころか増える(おばあさんに成り代わって、大量の出荷伝票を書くことを想像すれば分かる)から、それはサーバリソースとパフォーマンスと工数の無駄遣いでしかない。

なぜ、ボロカスにいうのか?

 RDBMSをタバコ屋さんに置き換えると、CPUが動かすおばあさんは、SQLという言語で書かれた注文書しか読めないですが、おばあさんがどのような作業をするかはC++で書かれたプログラムです。であれば、OO言語信者は SQL ができなくても、本当に高いレベルでOO言語ができるならば、

 http://otndnld.oracle.co.jp/document/products/oracle10g/102/doc_cd/server.102/B19215-02/logical.htm#51885

 の図の様にバラバラに保存されたデータに、どのようにアクセスするか、効率的なアクセス方法は持っているハズでしょう。C++のプログラム仕様書に当たるものが実行計画で、これは確認することができます。

 実行計画はプログラム仕様書ですからプログラミング言語C++である必要もない。Javaだろうが、.Netだろうが、Rubyだろうが、プログラム仕様書が効率的かどうかの判断はできなかったらまともなシステムは組めないし、プログラミングもできないはずです。

 SQLは魔法じゃなく、OO言語(C++)をラップしているものですから、それを更にOO言語でラップし直しても、理論的に効率的にはなりようがない。O/Rマッパ始め、各種のツール、フレームワークの類いのほとんどは、謂わば、おばあさんに渡す SQL に通訳するものに過ぎません。つまり、高級言語を低級言語でラップ(通訳)しているわけで、理論的に効率的になることはあり得ないのです。

 これが「あり得る」と考える人は、

    ■ 実行計画を見てない。

    ■ 見ても意味が分からない。

    ■ 効率的なプログラミングができない。

 のいずれかということが証明されている。ですから「それでも技術者か!」っていってるわけ。

 職業プログラマ(SE)というのは、顧客の話をコンピュータ言語に翻訳するのが仕事で、それに更に通訳を介した方が効率的か、通訳が不要になるようになった方が効率的かなんて、本来議論は不要なハズですけどね。

 こういうツールを作ろうと思うということは、SQLもOO言語の能力も大したことないことの証明でしかないでしょう。事実、私も新人の頃に企画したから、企画しようと考える意図は分かるけれど、レベル低かったなとしか思わない。もちろん、実際には、そんなツールを作る人は能力は高く下手糞に合わせるという商売をやっているのは理解できています。しかし、そのために誤った考え方を持つ下手糞を量産してしまうし、一巡すれば文化として定着してしまう。私はそんな無駄なことはしないで「その努力・コストを教育に向ける方が余程良い」と言ってるに過ぎません。

 繰り返すけれど、SQLを使わないで効率的にするには「おばあさんをスキップする」しかない。つまり、NoSQLを使うか、車輪の再開発になろうとも、データブロックに直にアクセする方法を確立するかしかなく、私が主張している条件外の話です。


 私は「無知の知」というのはとても重要だと考えていて、今できなくても「やらないと行けない」と考えている人は「できる人」同じと考えています。プロはカンニングもコピーもOKなんだから、すぐにできるるようになるハズです。COBOLerと言ってるけれど、COBOL をやっている人が COBOLer なのは当然で、何の問題もない。(私は金融系からスタートしたので、COBOLerは尊称だったのですが)

 しかし、絡んで来るのはそういう人達ではない。自分はできると思い込んで掛かってくるのが問題です。そういう人達に「実行計画を確認しているのか?」と何度も言ってきましたが、要するに、私に絡んで来るのは、

    ■ RDBMS の構造を理解してない COBOL をやらない COBOLer

    ■ 実行計画を確認してない。読めない。

    ■ 効率的なプログラム仕様書が書けない。

      (得意な言語でも効率的な処理が書けない)

 のいずれかで、そんな状態で【祭りだ!】【炎上だ!】って絡んでくるような馬鹿者が技術者を名乗るなんてあってはならないでしょう。

 SQLは最も簡単な言語だけれど得手不得手は当然あります。SQLができないなら、NoSQLに移行するか、担当を分ければいいわけで、「できないからラップする」という考え方はプロとして「右折ができないタクシードライバーと一緒」なのです。SQLを文法から考えて、答えが正しいかどうか、なんてやり方をしているとSQLの習得は確かに難しい。そういう考え方できた人達は、SQLは魔法だとしか考えてないのだろうし、「テーブル = ファイル」というイメージを持っているんじゃないかな。SQLは魔法じゃない、イメージから考えれば本当に簡単なのですけれど。


 勉強会でお話しするのは、そんなテクニカルな話ではなく、じゃあ「上流の設計に活かすにはどうしたら良いか」ということになります。

 詳しい内容が決まったら追記します。