概要
Python から MySQL に接続するためのドライバは複数存在している。どれを使うのが一番良いのか確認してみる。
Python の MySQLドライバ
Python の MySQLドライバの主な物は「MySQL - PythonInfo Wiki」に記載されている。
以下のような物がある。記事を書いた時点でのpypiでの最終リリース日の新しい順。
リンク | Python3対応 | DB API v2.0対応 | ライセンス | メンテナー | 最終リリース日 | 概要 |
---|---|---|---|---|---|---|
MySQL-python | × | ◯ | GPL or CNRI Python License | Andy Dustman | 2012-09-27 | 比較的初期から存在したため有名なドライバ |
mysql-connector-python | ◯ | ◯ | GNU GPLv2 (with FOSS License Exception) | Oracle社および協力者 | 2012-09-07 | MySQL公式配布物 |
oursql | ◯ | ◯ | BSD | Aaron Gallagher | 2012-06-05 | MYSQL_STMT API のラッピングにフォーカスしたドライバ |
umysql | 不明 | × | BSD License | Jonas Tarnstrom | 2012-04-20 | C/C++のみで実装されている。gevent対応と記載されている。 |
mysql-connector-repackaged | ◯ | ◯ | GNU GPLv2 (with FOSS License Exception) | Sun Microsystems社 | 2012-03-11 | 「mysql-connector-python」の古いバージョン |
PyMySQL3 | ◯ | ◯ | MIT | Pete Hunt | 2011-11-08 | Pythonのみで実装されている。Python2 対応版はPyMySQLで別配布。 |
PyMySQL | × | ◯ | MIT | Pete Hunt | 2011-11-08 | Pythonのみで実装されている。Python3 対応版はPyMySQL3で別配布。 |
とりあえず、Python 2.7.3 で動作を確認する。また DB API v2.0 対応は必須としたい。
この条件に合致する
「MySQL-python」、
「mysql-connector-python」、
「oursql」、
「PyMySQL」、
を比較してみる。
準備
利用した環境のバージョンは以下とした。今回の MySQL は localhost の物を利用する。
比較観点は、「実行速度」、「メモリ効率」、「使い勝手」。「使い勝手」に関しては「戻り値」、「with」、「例外」あたりを見ることにする。
- Mac OS X 10.8.3 Mountain Lion
- Python 2.7.3
- MySQL Community Server 5.5.27
- ホスト:localhost
- データベース名:example
- ユーザ名:username
- パスワード:password
- 利用ドライバ
- MySQL-python 1.2.4b2
- mysql-connector-python 1.0.6b2
- oursql 0.9.3.1
- PyMySQL 0.5
Python に今回利用するライブラリをインストール。
# ドライバ類のインストール
pip install MySQL-python
pip install mysql-connector-python
pip install oursql
pip install PyMySQL
# 計測ツール
easy_install Benchmarker
pip install psutil
pip install memory_profiler
データベースとテーブルを作成。
> mysql -u root
mysql> CREATE DATABASE example DEFAULT CHARACTER SET utf8;
mysql> GRANT ALL PRIVILEGES ON example.*
-> TO username@localhost
-> IDENTIFIED BY 'password';
mysql> exit;
> mysql -u username -ppassword example
mysql> CREATE TABLE tweets (
-> id serial PRIMARY KEY,
-> status_id VARCHAR(255) UNIQUE NOT NULL,
-> from_user_id VARCHAR(255) NOT NULL,
-> text VARCHAR(140) NOT NULL,
-> created_at VARCHAR(50) NOT NULL,
-> datetime DATETIME NOT NULL
->) engine=innodb default charset=utf8;
ソースコード
単純な INSERT、SELECT、DELETE のコードを以下のように書いてみた。
MySQL-pythonのサンプル
def sample_mysql_python():
import MySQLdb
cur = MySQLdb.connect(
host='localhost',
db='example',
user='username',
passwd='password').cursor()
print(cur.execute('SELECT * FROM tweets'))
# 日本語でも u つけない
cur.execute(
'INSERT INTO tweets '
'(status_id, from_user_id, text, created_at, datetime) '
'VALUES (%s, %s, %s, %s, current_timestamp)',
('251298602096xxxxx1', '43172xxx6',
'さんぷるでーた', 'Mon, 1 Oct 2012 12:33:50 +0000'))
print(cur.execute('SELECT * FROM tweets'))
res = cur.fetchall()
for row in res:
print(row[3])
cur.execute(
'DELETE FROM tweets WHERE status_id = %s',
('251298602096xxxxx1',))
cur.close()
oursqlのサンプル
def sample_oursql():
import oursql
with oursql.connect(
host='localhost',
db='example',
user='username',
passwd='password').cursor() as cur:
print(cur.execute('SELECT * FROM tweets'))
# 日本語は u つける
cur.execute(
'INSERT INTO tweets '
'(status_id, from_user_id, text, created_at, datetime) '
'VALUES (?, ?, ?, ?, current_timestamp)',
('251298602096xxxxx1', '43172xxx6',
u'さんぷるでーた', 'Mon, 1 Oct 2012 12:33:50 +0000'))
print(cur.execute('SELECT * FROM tweets'))
res = cur.fetchall()
for row in res:
print(row[3])
cur.execute(
'DELETE FROM tweets WHERE status_id = ?',
('251298602096xxxxx1',))
PyMySQLのサンプル。自分の環境では日本語が INSERT できなかった。
def sample_pymysql():
import pymysql
cur = pymysql.connect(
host='localhost',
db='example',
user='username',
passwd='password').cursor()
print(cur.execute('SELECT * FROM tweets'))
# TODO 日本語がうまく入らない?
cur.execute(
'INSERT INTO tweets '
'(status_id, from_user_id, text, created_at, datetime) '
'VALUES (%s, %s, %s, %s, current_timestamp)',
('251298602096xxxxx1', '43172xxx6',
'さんぷるでーた', 'Mon, 1 Oct 2012 12:33:50 +0000'))
print(cur.execute('SELECT * FROM tweets'))
res = cur.fetchall()
for row in res:
print(row[3])
cur.execute(
'DELETE FROM tweets WHERE status_id = %s',
('251298602096xxxxx1',))
cur.close()
mysql-connector-pythonのサンプル。自分の環境では INSERT 文が実行できなかった。
def sample_mysql_connector():
import mysql.connector
con = mysql.connector.connect(
host='localhost',
db='example',
user='username',
passwd='password',
buffered=True)
# 書き方が他と違うが、connect().cursor() 形式だとエラーになる模様
cur = con.cursor()
cur.execute('SELECT * FROM tweets')
# TODO 動作しない?「Unread result found」になる「buffered=True」で動作する
cur.execute(
'INSERT INTO tweets '
'(status_id, from_user_id, text, created_at, datetime) '
'VALUES (%s, %s, %s, %s, current_timestamp)',
('251298602096xxxxx1', '43172xxx6',
'さんぷるでーた', 'Mon, 1 Oct 2012 12:33:50 +0000'))
cur.execute('SELECT * FROM tweets')
res = cur.fetchall()
for row in res:
print(row[3])
cur.execute(
'DELETE FROM tweets WHERE status_id = %s',
('251298602096xxxxx1',))
cur.close()
con.close()
比較結果
「mysql-connector-python」は実行できなかったので、比較できないという結果になったが、どうやるのが正しいのだろう?このサンプルでは「buffered=True」が必要。
様々な条件で試した結果、速度は「MySQL-python」、「oursql」、「PyMySQL」の順。数値は載せないので、自分で試してほしい。
メモリ効率はこのサンプルだとそれほど違いがないようだ。
使い易さは「oursql」が圧倒的で、「with」が利用できるのはこのドライバだけ。いろいろ頑張っている分遅いと思われる。また 「oursql」は「libmysql」に依存しているのは欠点。
結論
速度を求める場合や、ORMを利用する場合は、「MySQL-python」を使うのが良い。Python 3 への対応は徐々に行なわれている模様。
ドライバだけで利用したい場合や Python 3 に対応したい場合は、「oursql」は良い選択肢になる。
「PyMySQL」と「mysql-connector-python」に関しては、今回上手く実行できていない部分があるので判断を保留とする。
2012/10/10追記:
「PyMySQL」と「mysql-connector-python」のエラーに関して補足しておく。
「PyMySQL」のエラー
「PyMySQL」は「"さんぷるでーた"」として日本語を INSERT して、SELECT しようとすると以下のようなエラーになる。
Traceback (most recent call last):
File "sample.py", line xxx, in <module>
main()
File "sample.py", line xxx, in main
sample_pymysql()
File "sample.py", line xxx, in sample_pymysql
print(cur.execute('SELECT * FROM tweets'))
File "/path/to/lib/python2.7/site-packages/pymysql/cursors.py", line 117, in execute
self.errorhandler(self, exc, value)
File "/path/to/lib/python2.7/site-packages/pymysql/connections.py", line 187, in defaulterrorhandler
raise Error(errorclass, errorvalue)
pymysql.err.Error: (<type 'exceptions.UnicodeEncodeError'>, UnicodeEncodeError('latin-1', u'\u3055\u3093\u3077\u308b\u3067\u30fc\u305f', 0, 7, 'ordinal not in range(256)'))
「u"さんぷるでーた"」として日本語を INSERT しようとすると以下のようなエラーになる。
Traceback (most recent call last):
File "sample.py", line xxx, in <module>
main()
File "sample.py", line xxx, in main
sample_pymysql()
File "sample.py", line xx, in sample_pymysql
u'さんぷるでーた', 'Mon, 1 Oct 2012 12:33:50 +0000'))
File "/path/to/lib/python2.7/site-packages/pymysql/cursors.py", line 97, in execute
escaped_args = tuple(conn.escape(arg) for arg in args)
File "/path/to/lib/python2.7/site-packages/pymysql/cursors.py", line 97, in <genexpr>
escaped_args = tuple(conn.escape(arg) for arg in args)
File "/path/to/lib/python2.7/site-packages/pymysql/connections.py", line 579, in escape
return escape_item(obj, self.charset)
File "/path/to/lib/python2.7/site-packages/pymysql/converters.py", line 35, in escape_item
val = val.encode(charset)
UnicodeEncodeError: 'latin-1' codec can't encode characters in position 1-7: ordinal not in range(256)
つまり、日本語の INSERT は可能な模様。しかし、「SELECT」ができない。
このサンプルのままだと、エラーが解消されなかったのでそれ以上の原因の調査はしていない。
「mysql-connector-python」のエラー
「mysql-connector-python」は、INSERTで以下のようなエラーになる。
Traceback (most recent call last):
File "sample.py", line xxx, in <module>
main()
File "sample.py", line xxx, in main
sample_mysql_connector()
File "sample.py", line xxx, in sample_mysql_connector
'さんぷるでーた', 'Mon, 1 Oct 2012 12:33:50 +0000'))
File "/path/to/lib/python2.7/site-packages/mysql/connector/cursor.py", line 365, in execute
raise errors.InternalError("Unread result found.")
mysql.connector.errors.InternalError: Unread result found.
その後 ORM の調査をしているが、「MySQL-python」以外のドライバを利用しようとすると、結構面倒なので、「MySQL-python」以外のドライバに関して興味を喪失したため、その後調査していない。
2012/10/11追記:
「PyMySQL」charsetの設定をしたら通った。「PyMySQL」では明確に指定する必要があるのですね。
con = pymysql.connect(
host='localhost',
db='example',
user='username',
passwd='password',
charset='utf8')
「mysql-connector-python」の方はcharsetやportを指定してもエラーになるので別の原因らしい。
2013/05/29追記:
「mysql-connector-python」はこのサンプルでは「buffered=True」が必要。