Hatena::ブログ(Diary)

憂鬱なプログラマの形而上学 RSSフィード

2010-08-23

MySQLにおける10の最適化設計

さて、ここでコーヒーブレークして、MySQL最適化について考えてみたいと思います。MySQLデータベース分散処理では、SQLを複数のサーバーやテーブルへと分散する方法についてご紹介させて頂きましたが、こちらは主に、個々のDBサーバー最適化について考えてみたいと思います。

大きくわけると、MySQLにおいては、以下のことがポイントになるかなと思います。(レプリケーションなどの分散系は他でさんざん触れているのでここでは割愛します)

重要だと思う順番に列挙します。

1.とにかく分析。できるだけ実際に近いデータでEXPLAINの結果を見たり、slow-queryを分析します。また、CPUの使用状況として、vmstatなどで、できるだけ常にiowaitが0近い状態にします。

MySQLは癖が強いので、特に慣れていない人はセオリー通りにいかないことは結構あると思います。ですので、実際の分析を何よりも重視してください。また、INDEXの使われ方やパフォーマンスは、データ数やSQL発行順序において、できるだけ実際の環境に近い状況を作り出してテストを行わないと、意味がないので注意してください。

例えば、私の場合であれば、アプリケーションデバッグモードにすることで、自動的にすべてのSQLが記録され、さらにすべてのSELECT文に対して、自動的にEXPLAINの結果を保存するような仕組みとなっています。これで想定にできるだけ近いデータを使い、JMeterなどを使って負荷をかけることで、実際にどのようなSQLがどのくらいの効率で実行されているかを検証することができます。

2.SQLの回数はできるだけ減らす。MySQLには、REPLACEやINSERT IGNORE、DUPLICATE KEYS UPDATEなどの方言も多数存在します。

例えば、SELECT文で予めレコードが存在するか確認して、存在すればUPDATE、しなければINSERT、みたいな方法であれば、そのSELECTの条件にUNIQUE制約をかけてREPLACE文を発行すればSQL一つで完了します。

同じようにINSERT IGNOREやDUPLICATE KEYS UPDATE なども使ってSQLの回数を減らすことができます。

これは、たとえばforeachなどでSELECT文を複数回発行している場合についても同様のことが言えます。

3.それぞれのSQLに的確なINDEX。ひとつひとつのSQLに的確な複合INDEXを張ります。

MySQLではPostgresやOracleなどと違って、一つのSQL(一つのテーブルごと)に一つのINDEXしか使われません。

例えば、以下のようなSQLがあったとします。

SELECT ... FROM examples WHERE a = 1 AND b = 2 AND c = 3 ORDER BY d;

このSQL最適化させるためには、a,b,c,dに個別にINDEXを張るのではなく以下のようなINDEXを生成する必要があります。(逆に以下のINDEXがあれば、上記のSQLに関しては、a,b,c,d個別のINDEXは不要です)

ALTER TABLE examples ADD KEY (a,b,c,d);

複合INDEXは、実際のSQLと同じ順番でなければうまく適応されません。例えば上記の例でいえば、必ず a,b,c,dの順序でINDEXを生成しておく必要があります。

4.confファイルの設定ではまず、バッファサイズを最適化する。

confファイルのチューニングで最も重要なのはバッファサイズです。

より詳細な情報は以下のページを参考にするとよいかと思います。

DSAS開発者の部屋:5分でできる、MySQLのメモリ関係のチューニング!

5.前方一致でないLIKE検索はできるだけ避ける。

LIKE検索においては、検索内容が % で始まる場合、INDEXが使われません。

SELECT ... FROM examples WHERE e LIKE '%hoge%'

このような場合、できればSennaなどによる全文検索を利用するか、正規化、あるいはその両方を行ったほうがよいでしょう。

6.正規化などを駆使してできるだけ重いSQLを個別のテーブルに切り離す。

これは前項のような重いLIKE検索が走るような場合にも言えますが、MySQLの場合、以下のようにロックがかかります。

  • MyIsam
    • すべてのSELECT/INSERT/UPDATE/DELETE文において、常にテーブルロック
  • InnoDB
    • プライマリーキーによるSELECT => 行ロック
    • それ以外のSELECT/INSERT/UPDATE/DELETE文 => テーブルロック

なので、LIKE検索や全文検索のような重いSQLはできるだけ、それ専用のテーブルを別に作ったほうがよいかと思います。

7.INはINDEXが有効でしかも結構速いが、ORは左辺と右辺のどちらかのみのINDEX適応(基本的には左辺)となる。

できるだけORを使うSQLを発行しなくてすむような設計にすることが望ましいですが、多くの要件では100%は難しいと思うので、その場合には、ANDで結合できる部分をできるだけ前に持ってきて、ORの後ろの条件をできるだけ簡潔なものとする(なぜなら原則としてORの後ろはINDEXが聞かないから)

また、ORで複数の=を使う場合、IN演算子を使ったほうがはるかに高速に動作します。

SELECT ... FROM examples WHERE a = 1 OR a = 2 OR a = 3 ;

よりも

SELECT ... FROM examples WHERE a IN (1,2,3);

のほうが高速で、かつINDEXが的確に適応されます。

8.出来るだけテーブルを固定長になるようにする

可変長のテーブルよりも固定長のテーブルのほうがSELECT文が速く実行できます。

MySQLにおける固定長のテーブルとは、TEXT型やBLOB型のような可変長カラムを含まないテーブルのことです。

9.ポイントポイントでキャッシュする。

例えば、SNSであれば、メールの未読件数について毎回、「SELECT COUNT(1) ...」というSQLを発行する代わりにユーザーのテーブルにmail_notread_numのようなカラムを用意しておき、メールの受信をトリガーにキャッシュ用カラムを更新する、といった設計にすることで、比較的重いCOUNT()関数SQLの発行回数を削減できます。

同じように、別のテーブルのカラムだったり、あるいはmemcachedやファイルなどにSQLの結果などをキャッシュしておくことができます。

10.出来るだけSQLが単純になるようにする。また、サブクエリはできるだけ使用せず、複数のテーブルを同時参照する場合にはINNER JOIN、LEFT JOIN、RIGHT JOINなどを使い、結合条件としてもUSINGを使うか、できるだけ単純な条件とする。

MySQLの基本的な仕組みとして、設計を単純にすることで(いや、そんなに単純でもないんだけど)高速な動作を実現している、という側面があります。

なので、複雑な構文はさけ、できるだけ単純な構文のみを使います。

また、私自身は冗長化を考慮してできるだけ結合などはしないようにしているのですが、それでも複数のテーブル参照が必要な場合は、できるだけサブクエリではなくJOIN系を使うようにしています。

おすすめ本

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証

トラックバック - http://d.hatena.ne.jp/k_yamamot/20100823/1282498466