レガシーコードからの脱却

レガシーな職場でも独学でどうにか頑張っていくブログです。

【読書記録】SQL 実践入門 高速でわかりやすいクエリの書き方

SQL 実践入門 高速でわかりやすいクエリの書き方 ミック[著]

お盆休みということで、2年前くらいに購入して一度読んだきり置いていた『SQL 実践入門』を読み返しました。 本書では主にOraclePostgreSQLで説明されていますが、SQLServerMySQLくらいしか触った経験のない私でも十分読めました。

前半ではDBMSの主な仕組みと基本的なSQLの構文をおさらいした後に、中盤以降は実行計画をみながらよりパフォーマンスのいいクエリとは何かを実例とともに紹介してくれています。

目次は以下の通りです。

  • 第1章:DBMSアーキテクチャ──この世にただ飯はあるか
  • 第2章:SQLの基礎──母国語を話すがごとく
  • 第3章:SQLにおける条件分岐──文から式へ
  • 第4章:集約とカット──集合の世界
  • 第5章:ループ──手続き型の呪縛
  • 第6章:結合──結合を制する者はSQLを制す
  • 第7章:サブクエリ──困難は分割するべきか
  • 第8章:SQLにおける順序──甦る手続き型
  • 第9章:更新とデータモデル──盲目のスーパーソルジャー
  • 第10章:インデックスを使いこなす──秀才の弱点

いちいち、各章のサブタイトルがかっこいい!

以下、メモまたは感想です。

第5章:手続き型の呪縛

パフォーマンスが悪いという知らせを受けて確認してみたら、ぐるぐる系(レコード数分繰り返し処理を行うような実装方法)で書かれていたということは経験あります。

その際、ガツン系のSQLに書き換えるために、相関サブクエリを使ってみたものの、複雑になって結果パフォーマンスはあんまり変わらずということがしょっちゅうでしたので、この章のウィンドウ関数の使用例は感動でした。(ROWS BETWEENを初めて知りました。) SQL Serverでも色々対応されているようなので、ウィンドウ関数をよく勉強しなくて使いこなせるようにならないといけませんね。

第6章:結合を制する者はSQLを制す

スラダンを思い出す章題です(笑)

スカラサブクエリ

結合とは直接的に関係しませんが、以下の相関サブクエリについて書かれた記述が地味に大切だと感じました。

相関サブクエリをスカラサブクエリとして使うと、結果行数の数だけ相関サブクエリを実行することになるため、かなり高コストな処理になるからです。

Nested Loops

SQLチューニングの基本中の基本は「駆動表の小さなNested Loops」+ 「内部表の結合キーにインデックス」!!

Hash

大規模テーブル同士の結合に適している。

第7章:困難は分割するべきか

サブクエリ・パラノイアってどっかで見たことあるぞと思い調べてみましたら、こっちも著者がミック氏でした。

第1回 サブクエリ・パラノイア~副問い合わせ乱用による性能劣化を治療せよ!:SQL緊急救命室|gihyo.jp … 技術評論社

サブクエリのパフォーマンスに対する使いどころとして、先に結合テーブルのレコードを絞るということが書かれていました。

第9章:盲目のスーパーソルジャー

「データモデルを制する者はシステムを制す」は日々感じているので(主に被害者として)、常に心がけていきたいですね。

第10章:秀才の弱点

インデックス利用が有効かの判断基準 カーディナリッティが高く、選択率が10%前後

インデックスが使えないパターンも忘れがちなのでメモしておきます。 どれもインデックスがどんなものかを考えれば、まぁわかりますね。

  • 中央一致、後方一致 ⇒前方の値が決まらないため
  • 索引列で演算 ⇒インデックスに存在するのは演算結果ではなく、計算前の列の値だから
  • 索引列で関数を使用している ⇒上に同じ
  • IS NULL ⇒索引データにNULLは作成されないため
  • 否定形を用いる(<> != NOT IN)

さいごに

約2年ぶりに読み直しましたが、忘れていたりするところもあって大変勉強になりました。 全てのところで具体例とともに説明されているので、非常にわかりやすいです。 実際に時間も2時間ほどで読めたので、大型本に比べて読破のハードルも低いと思います。

そのため、会社の後輩や新人にも是非読んでもらいたいです。(本当は先輩も…)

またミックさんの講演会が8/25にあるというメールが来ていましたが、用事で参加できないのは残念です。

次は、『達人に学ぶSQL徹底指南書』か『SQLアンチパターン』を読みたいですね。

あとはSQLServerも行式サポートしてくれないかなぁー

【お勉強記録】C#文法学び直し。並列処理2

前回に引き続き並列処理について

Task

Taskクラスは.NET Framework 4から使えるクラスで、重い処理を最小限に抑えるためにうまくやってくれているそうです。

前回のを書き換えてみました。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ThreadTest
{
    class Program
    {
        static void Main(string[] args)
        {
            object obj = new object();

            Task task = Task.Factory.StartNew(() => { new Counter("タスク", 5).Play(); });
            new Counter("メイン", 3).Play();

            Console.ReadLine();
        }
    }
}

実行するとうまく並行処理できています。

StartNewメソッドは引数としてActionデリゲートが指定されています。 上の例ではTaskのインスタンスを作って実行していますが、Task.Runを使用すると、タスクの作成から実行までやってくれます。

static void Main(string[] args)
{
    Task.Run(
        () => { new Counter("タスク", 3).Play(); }            
        );
    new Counter("メイン", 3).Play();
    Console.ReadLine();
}

同期についてですが、TaskクラスのWaitを用いることでそのタスクの終了を待つことができるようです。

Task task = Task.Factory.StartNew(() => { new Counter("タスク", 5).Play(); });
new Counter("メイン", 1).Play();
task.Wait();
new Counter("同期後処理", 3).Play();
Console.ReadLine();

実行するときちんとタスクの処理が終了してから同期後処理と出力されるのが確認できます。

Waitメソッドによってそのタスクが終了するのを待ってから処理を行うことができるのは上の通りです。

複数タスクを作ったときはすべてWaitを実装しなくてはならないのかと思いきや、Taskクラスでは、複数タスクを同期するため「WaitAll」メソッドが用意されているとのこと。

        static void Main(string[] args)
        {
            
            Task taskA = Task.Factory.StartNew(() => { new Counter("タスクA", 3).Play(); });
            Task taskB = Task.Factory.StartNew(() => { new Counter("タスクB", 3).Play(); });
            Task taskC = Task.Factory.StartNew(() => { new Counter("タスクC", 3).Play(); });
            Task taskD = Task.Factory.StartNew(() => { new Counter("タスクD", 3).Play(); });
            //全てのタスクの終了を待つ
            Task.WaitAll(taskA, taskB, taskC, taskD);
            new Counter("同期後処理", 2).Play();  
          
            Console.ReadLine();
        }

f:id:Gappory:20170812173643p:plain

ちなみにTask.WaitAnyメソッドも用意されていて、これは文字通り引数に渡されたタスクが一つでも終了した時点をとらえられるようです。

Parallelクラス

Parallelクラスを使うことで、複数タスクをよりシンプルに書くことができます。

Parallel.Invoke(
    () => { new Counter("タスクA", 3).Play(); },
    () => { new Counter("タスクB", 3).Play(); },
    () => { new Counter("タスクC", 3).Play(); },
    () => { new Counter("タスクD", 3).Play(); }                
    );                                             
Console.ReadLine();

ParallelクラスにはForやForEachメソッドも用意されています。

async と await

C# 5.0から非同期メソッドが導入され、これによってさらにシンプルに並行処理を実現できるようになったそうです。

非同期メソッドにはasync修飾子をつけ、処理内でTaskにawaitをつけることで、Taskの完了を待つことができます。 非同期メソッド内でタスクの順番を指定できるというイメージでしょうか? 難しい…

とりあえず、awaitの有無の差を書いて試してみました。

        static void Main(string[] args)
        {
            Task task = Program.PlayAsync();            
            Console.ReadLine();
        }

        static async Task PlayAsync()
        {
            for (int i = 0; i < 10; i++)
            {
                int x = i;
                //awaitを付ける。
                await Task.Run(() => new Counter("タスク" + x.ToString(), 2).Play()); 
            }
        }

f:id:Gappory:20170812191723p:plain

        static void Main(string[] args)
        {
            Task task = Program.PlayAsync();            
            Console.ReadLine();
        }

        static async Task PlayAsync()
        {            
            for (int i = 0; i < 10; i++)
            {
                int x = i;
                //awaitを付けない。
                Task.Run(() => new Counter("タスク" + x.ToString(), 2).Play());
            }            
        }        

f:id:Gappory:20170812191849p:plain

なんとなくですが、違いが見えま…す?

非同期メソッドの戻り値はTaskかTask型のみみたいです。 そのため、非同期メソッド自体をTaskとして扱えます。

        static void Main(string[] args)
        {            
            Task task = Program.PlayAsync();
            new Counter("メイン", 10).Play();
            Console.ReadLine();
        }

        static async Task PlayAsync()
        {            
            for (int i = 0; i < 10; i++)
            {
                int x = i;
                await Task.Run(() => new Counter("タスク" + x.ToString(), 2).Play());
            }            
        }        

むっずーーー!!! async/awaitに関しては正直100%の理解はまだ私には難しいですね… 非同期メソッドの書き方はまぁまぁわかるのですが、きっと利用のされ方ですね鍵は…??

書籍や下記のサイトを使って勉強してみましたが、非同期メソッド結構難しいです。

まだ感覚的理解の枠を抜け出せていない気がします。

ソース等で見かけた際はじっくり読むようにしてしっかり理解したいです。

参考サイト

非同期メソッド - C# によるプログラミング入門 | ++C++; // 未確認飛行 C

Taskを極めろ!async/await完全攻略 - Qiita