footmark

ひよっこエンジニアの足跡

【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を操作する

PythonMySQLドライバ、mysql-connector-pythonを使っていて躓いたので、その備忘録も兼ねて紹介します。

PythonMySQLを操作しようと思ったら

まずドライバをインストールしなければいけません。
いろんな種類があり、紹介マニアどらふと版: 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正規形

定義

リレーションがスカラ値のみを持ちうる

リレーションの正規化 - Wikipedia

用語

  • スカラ値
    • それ以上分割できない値

リレーションの正規化 - Wikipedia

つまり

全ての属性(列)の値が、複数の値や他の属性から導出できる値でない状態。

非正規形の状態では、部活動属性に複数の値を持つ学生がいるので分割します。

学籍番号 名前 地区コード 住所 部活動
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正規形で、かつ、すべての非キー属性が、すべての候補キーに対して完全従属する

リレーションの正規化 - Wikipedia

用語

  • 関数従属性
    • 一方の値が決まると他の項目の値も一意に決まる関係

正規化

  • 完全従属
    • ある値がある候補キーにおける全ての属性によって一意に決まる関係
  • 部分従属
    • ある値がある候補キーにおける一部の属性によって一意に決まる関係
  • 候補キー
    • 組(行)の識別のために必要な属性またはその集合
  • 非キー属性
    • 候補キーでない属性

候補キー - Wikipedia

まずリレーションにおける候補キーを探します。
今回は

  • 学籍番号

だけですね。
もし同姓同名の学生が存在しない状況であれば、

の集合も候補キーとなります。

話が逸れました。
候補キーが学籍番号属性のみなので、非キー属性が候補キーに完全従属しているのは自明です。
よって第1正規形の段階で第2正規形の条件を満たしていたことになります。
(例が悪いw)

第3正規形

定義

リレーションが、第2正規形で、かつ、非キー属性があるならば、それら全てが候補キーに非推移的に関数従属する

リレーションの正規化 - Wikipedia

用語

  • 推移的に関数従属する
    • 候補キーA及び非キー属性B, Cを含むリレーションがあり、A → BかつB → Cのとき、Cは候補キーAに推移的に関数従属する
  • 非推移的に従属する
    • 関数従属するが推移的に関数従属していないこと

リレーションの正規化 - Wikipedia

今回の例では候補キー 学籍番号から非キー属性 地区コードが求められ、地区コードから非キー属性 住所が求められます。
よって住所が学籍番号に推移的に関数従属しているので、地区コードと住所の関係を別表に移すことによりこの関係を除去します。

学籍番号 地区コード 部活動
101 鈴木 太郎 1 野球部
101 鈴木 太郎 1 吹奏楽
102 田中 一郎 2 サッカー部
102 田中 一郎 2 軽音楽部
103 吉田 健太 3 水泳部

地区コード 住所
1 地方A
2 地方B
3 地方C

これで第3正規形の完成です。

まとめ

第1正規形

  • 複数の値を分割する
  • 他の属性から導出できる属性を除去する

第2正規形

  1. 候補キーを探す
    • 候補キー:特定の行を一意に求めることができる属性や属性の集合
  2. 非キー属性が候補キーの一部から求まる関係をなくす(候補キーが集合である場合)
    • 非キー属性:候補キーでない属性

第3正規形

  1. 非キー属性から他の非キー属性が求まる関係をなくす

あとがき

導出例だけで勉強したときに生まれた冗長を消す意味でのアウトプットだったんですが、結果、定義と用語説明が引用のオンパレードになってしまいました。。。
つまり基礎が大切ってことなんですが、分かりやすさが微塵もないので要リファクタリングですね。

Sublime Text 3 + SFTP プラグインでHDD

僕は仮想マシンや外部サーバにSSHログインをして作業することが多いので、似非Vimmerを名乗っていたんですが、なんとなく「sublime text ssh」でググったところ、巷で人気のSublime Textに浮気しても問題なさそうではありませんか!!!

結果

浮気しました。

続きを読む

【Virtualenv】Pythonをコンパイルして環境構築

Python 3系を使いたい。
だけどマシンに入ってるのは2系。

バージョンを上げればいいんだけど、諸々の事情で環境を汚せない。
そんなときのためのVirtualenv!

っていうのは知ってたんですが、いざやろうと思うと分からない。
あれ?自分より高いバージョン持てんの?みたいな。

どうやら自分でコンパイルして出てきた実行ファイルをホームディレクトリなんかに置いとけばいいようです。
確かに。隠し持っておけば問題ないよね。

続きを読む