【MySQL】インデックスを貼ったら32.2倍で動いた
レコードを数えたり、重複を削除したりと便利なMySQLですが、思ったほど速度が出ない状況があります。
そんなときのKVSか!と思ったのですが、インデックス周りを見直すだけでもかなりチューニングできそうなので検証してみました。
テストデータ
処理
キーワードが与えられると、そのキーワードが各文書にどれだけ含まれているかを返す処理を想定します。
テーブル構造
文書と単語の中間テーブル
doc_word_id | doc_id | word_id |
---|---|---|
1 | 1 | 1 |
2 | 1 | 2 |
3 | 1 | 3 |
4 | 2 | 4 |
5 | 2 | 2 |
単語テーブル
word_id | surface | pos |
---|---|---|
1 | 私 | 名詞 |
2 | 名前 | 名詞 |
3 | 鈴木 | 名詞 |
4 | 趣味 | 名詞 |
5 | ギター | 名詞 |
中間テーブルのレコード数
count(doc_word_id) |
---|
143317 |
単語テーブルのレコード数
count(word_id) |
---|
4792 |
検証
上記の処理をするプログラムをPythonで書いて、time コマンドで実行時間を測ってみます。
結果はこんな感じになりました。
(realのみ掲載)
番号 | index | 実行時間 |
---|---|---|
1 | なし | 5m21.873s |
2 | doc_id | 2m55.429s |
3 | surface | 2m30.906s |
4 | doc_id, surface | 0m57.415s |
5 | 複合(doc_id, word_id) | 1m14.861s |
6 | 複合(doc_id, word_id), surface | 0m10.287s |
6番めっちゃ速い!
複合キー万歳ですね。
もう1点、
「MySQLでは、1つのクエリを実行するとき、1つのテーブルにつき1つのインデックスしか仕様できないのである。」
実践ハイパフォーマンスMySQL P66
とありますが、内部結合したときは結合後で1つのテーブルとして扱われるのかも気になっていました。
2番、3番と比べて4番が早くなっていることから結合前のテーブル数でカウントされている(結合後で1つのテーブルとして扱われていない)ようですね。
まとめ
- 複合キー速い!
- 内部結合するときはそれぞれのテーブルにインデックスを貼ると速い!
【mysql-connector-python】PythonからMySQLを操作する
PythonのMySQLドライバ、mysql-connector-pythonを使っていて躓いたので、その備忘録も兼ねて紹介します。
PythonでMySQLを操作しようと思ったら
まずドライバをインストールしなければいけません。
いろんな種類があり、紹介マニアどらふと版: Python の MySQL ドライバはどれを利用すれば良いのか に分かりやすくまとめられています。
僕はmysql-connector-pythonをチョイスしました。
特徴はMySQL公式のドライバだという点ですね。
公式がいいのか!と言われるとケースバイケースだと思いますが、最近使っているMySQL Workbenchという公式のGUIツールが使いやすかったので。
インストール
pipでいけます。
$ pip install mysql-connector-python
基本的な使い方
次のようなデータをinsertし、それをselectするコードを書いてみます。
(テーブルは作成済みとする)
student_id | name | sex |
---|---|---|
1 | 山田太郎 | 男 |
test.py
#!/usr/bin/env python # -*- coding: utf-8 -*- # ドライバをimport import mysql.connector if __name__ == '__main__': # データベースに接続 connect = mysql.connector.connect(user='hoge', password='hoge', host='hoge', database='hoge', charset='utf8') cursor = connect.cursor() name = '山田太郎' sex = '男' # insert cursor.execute('insert into student_table (name, sex) values (%s, %s)', (name, sex)) # select cursor.execute('select * from student_table') row = cursor.fetchone() # 出力 for i in row: print(i) # データベースから切断 cursor.close() connect.close()
実行結果
$ python test.py 1 山田太郎 男
今回はレコードを1つinsertし、それをselectするというものだったのでfetchone 関数を使いましたが、複数のレコードを取得したい場合はfetchall 関数を使います。
# select cursor.execute('select * from student_table') rows = cursor.fetchall() # 出力 for i in rows: print(i[0]) print(i[1]) print(i[2])
詰まったところ
上のコードでも使った
# insert cursor.execute('insert into student_table (name, sex) values (%s, %s)', (name, sex))
execute 関数の第1引数内の変数を %s とし、第2引数に変数を入れることで、特定の文字列をエスケープできるのですが、第2引数はタプルで表現しなければならないんですよ。
つまり、第二引数に変数を一つだけ与えたい場合、
(name)
や
name
ではエラーになってしまいます。
で、正解はこう。
(name,)
タプル恐ろしや。
【備忘録】第3正規形
RDBのテーブル設計における第3正規形の導出ですが、いつも導出例を見ながら雰囲気でやってしまうので、一回きちんと勉強しようと思った足跡的な記事。
- 第3正規形までの持ってき方忘れた
- 第3正規形ってこれであってる?
となった時のための備忘録。
非正規形
以下の表を例に正規化を進めていきます。
学籍番号 | 性 | 名 | 名前 | 地区コード | 住所 | 部活動 |
---|---|---|---|---|---|---|
101 | 鈴木 | 太郎 | 鈴木太郎 | 1 | 地方A | 野球部、吹奏楽部 |
102 | 田中 | 一郎 | 田中一郎 | 2 | 地方B | サッカー部、軽音楽部 |
103 | 吉田 | 健太 | 吉田健太 | 3 | 地方C | 水泳部 |
第1正規形
つまり
全ての属性(列)の値が、複数の値や他の属性から導出できる値でない状態。
例
非正規形の状態では、部活動属性に複数の値を持つ学生がいるので分割します。
学籍番号 | 性 | 名 | 名前 | 地区コード | 住所 | 部活動 |
---|---|---|---|---|---|---|
101 | 鈴木 | 太郎 | 鈴木太郎 | 1 | 地方A | 野球部 |
101 | 鈴木 | 太郎 | 鈴木太郎 | 1 | 地方A | 吹奏楽部 |
102 | 田中 | 一郎 | 田中一郎 | 2 | 地方B | サッカー部 |
102 | 田中 | 一郎 | 田中一郎 | 2 | 地方B | 軽音楽部 |
103 | 吉田 | 健太 | 吉田健太 | 3 | 地方C | 水泳部 |
また、名前属性はそれぞれ性属性と名属性から導出できるので除去します。
学籍番号 | 性 | 名 | 地区コード | 住所 | 部活動 |
---|---|---|---|---|---|
101 | 鈴木 | 太郎 | 1 | 地方A | 野球部 |
101 | 鈴木 | 太郎 | 1 | 地方A | 吹奏楽部 |
102 | 田中 | 一郎 | 2 | 地方B | サッカー部 |
102 | 田中 | 一郎 | 2 | 地方B | 軽音楽部 |
103 | 吉田 | 健太 | 3 | 地方C | 水泳部 |
これで第1正規形の完成です。
第2正規形
用語
- 関数従属性
- 一方の値が決まると他の項目の値も一意に決まる関係
- 完全従属
- ある値がある候補キーにおける全ての属性によって一意に決まる関係
- 部分従属
- ある値がある候補キーにおける一部の属性によって一意に決まる関係
- 候補キー
- 組(行)の識別のために必要な属性またはその集合
- 非キー属性
- 候補キーでない属性
例
まずリレーションにおける候補キーを探します。
今回は
- 学籍番号
だけですね。
もし同姓同名の学生が存在しない状況であれば、
- 性
- 名
の集合も候補キーとなります。
話が逸れました。
候補キーが学籍番号属性のみなので、非キー属性が候補キーに完全従属しているのは自明です。
よって第1正規形の段階で第2正規形の条件を満たしていたことになります。
(例が悪いw)
第3正規形
用語
- 推移的に関数従属する
- 候補キーA及び非キー属性B, Cを含むリレーションがあり、A → BかつB → Cのとき、Cは候補キーAに推移的に関数従属する
- 非推移的に従属する
- 関数従属するが推移的に関数従属していないこと
例
今回の例では候補キー 学籍番号から非キー属性 地区コードが求められ、地区コードから非キー属性 住所が求められます。
よって住所が学籍番号に推移的に関数従属しているので、地区コードと住所の関係を別表に移すことによりこの関係を除去します。
学籍番号 | 性 | 名 | 地区コード | 部活動 |
---|---|---|---|---|
101 | 鈴木 | 太郎 | 1 | 野球部 |
101 | 鈴木 | 太郎 | 1 | 吹奏楽部 |
102 | 田中 | 一郎 | 2 | サッカー部 |
102 | 田中 | 一郎 | 2 | 軽音楽部 |
103 | 吉田 | 健太 | 3 | 水泳部 |
地区コード | 住所 |
---|---|
1 | 地方A |
2 | 地方B |
3 | 地方C |
これで第3正規形の完成です。
まとめ
第1正規形
- 複数の値を分割する
- 他の属性から導出できる属性を除去する
第2正規形
- 候補キーを探す
- 候補キー:特定の行を一意に求めることができる属性や属性の集合
- 非キー属性が候補キーの一部から求まる関係をなくす(候補キーが集合である場合)
- 非キー属性:候補キーでない属性
第3正規形
- 非キー属性から他の非キー属性が求まる関係をなくす
あとがき
導出例だけで勉強したときに生まれた冗長を消す意味でのアウトプットだったんですが、結果、定義と用語説明が引用のオンパレードになってしまいました。。。
つまり基礎が大切ってことなんですが、分かりやすさが微塵もないので要リファクタリングですね。