├── requirements.txt ├── .gitignore ├── db └── 000_initialize.sql ├── mysql_drivers_comparison.py └── README.md /requirements.txt: -------------------------------------------------------------------------------- 1 | PyMySQL==0.6.1 2 | SQLAlchemy==0.9.3 3 | gevent==1.0 4 | greenlet==0.4.2 5 | mysql-connector-repackaged==0.3.1 6 | oursql==0.9.3.1 7 | wsgiref==0.1.2 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | __pycache__ 21 | 22 | # Installer logs 23 | pip-log.txt 24 | 25 | # Unit test / coverage reports 26 | .coverage 27 | .tox 28 | nosetests.xml 29 | 30 | # Translations 31 | *.mo 32 | 33 | # Mr Developer 34 | .mr.developer.cfg 35 | .project 36 | .pydevproject 37 | -------------------------------------------------------------------------------- /db/000_initialize.sql: -------------------------------------------------------------------------------- 1 | # create database mysql_drivers_test; 2 | # mysql -uroot mysql_drivers_test < db/000_initialize.sql 3 | DROP TABLE IF EXISTS `tests`; 4 | CREATE TABLE `tests` ( 5 | `id` varchar(255) COLLATE utf8_unicode_ci NOT NULL, 6 | `name` varchar(255) COLLATE utf8_unicode_ci NOT NULL, 7 | `test_id` varchar(255) COLLATE utf8_unicode_ci NOT NULL, 8 | `expires_at` datetime DEFAULT NULL, 9 | `created_at` datetime NOT NULL, 10 | `updated_at` datetime NOT NULL, 11 | `bool_prop` tinyint(1) NOT NULL DEFAULT '0', 12 | `type` varchar(255) COLLATE utf8_unicode_ci DEFAULT '', 13 | PRIMARY KEY (`test_id`,`id`) 14 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; 15 | -------------------------------------------------------------------------------- /mysql_drivers_comparison.py: -------------------------------------------------------------------------------- 1 | import greenify;greenify.greenify() 2 | from gevent import monkey;monkey.patch_all() 3 | from sqlalchemy import create_engine 4 | from gevent.pool import Pool 5 | import time 6 | import logging 7 | 8 | 9 | total_transactions = 100 10 | concurrency = 20 11 | 12 | 13 | def visit_mysql(db, item_id): 14 | conn = db.connect() 15 | tran = conn.begin() 16 | test_id = 'example' 17 | try: 18 | # | id | varchar(255) | NO | PRI | NULL | | 19 | # | name | varchar(255) | NO | | NULL | | 20 | # | test_id | varchar(255) | NO | PRI | NULL | | 21 | # | expires_at | datetime | YES | | NULL | | 22 | # | created_at | datetime | NO | | NULL | | 23 | # | updated_at | datetime | NO | | NULL | | 24 | # | bool_prop | tinyint(1) | NO | | 0 | | 25 | # | type | varchar(255) | YES | | | | 26 | conn.execute("insert into tests values('%s', '%s', '%s', null, now(), now(), 0, '')" % 27 | (item_id, item_id, test_id)) 28 | tran.commit() 29 | conn.execute("select id, name, test_id, expires_at, created_at, updated_at, bool_prop, " 30 | "type from tests").fetchall() 31 | conn.execute("select sleep(0.5)") 32 | except: 33 | tran.rollback() 34 | logging.exception("visit mysql") 35 | finally: 36 | conn.close() 37 | 38 | 39 | def clear_database(db): 40 | conn = db.connect() 41 | tran = conn.begin() 42 | try: 43 | conn.execute("delete from tests") 44 | tran.commit() 45 | except: 46 | tran.rollback() 47 | logging.exception("tear down") 48 | finally: 49 | conn.close() 50 | 51 | 52 | def g_exception_handler(greenlet): 53 | logging.warn('{0}'.format(greenlet.exception)) 54 | 55 | 56 | def test_mysql_with(uri): 57 | db = create_engine(uri, pool_size=concurrency, max_overflow=10) 58 | clear_database(db) 59 | 60 | start = time.time() 61 | 62 | pool = Pool(concurrency) 63 | 64 | for i in xrange(total_transactions): 65 | g = pool.spawn(visit_mysql, db, "test%d" % i) 66 | g.link_exception(g_exception_handler) 67 | 68 | pool.join() 69 | time_elapse = time.time() - start 70 | print "%s total %d (%d) %.4f seconds" % (uri, total_transactions, concurrency, time_elapse) 71 | 72 | 73 | if __name__ == "__main__": 74 | connection_uris = [ 75 | "mysql://root:@localhost:3306/mysql_drivers_test", 76 | "mysql+pymysql://root:@localhost:3306/mysql_drivers_test", 77 | "mysql+oursql://root:@localhost:3306/mysql_drivers_test", 78 | # "mysql+cymysql://root:@localhost:3306/mysql_drivers_test", 79 | "mysql+mysqlconnector://root:@localhost:3306/mysql_drivers_test" 80 | ] 81 | map(test_mysql_with, connection_uris) 82 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | sqlalchemy-gevent-mysql-drivers-comparison 2 | ========================================== 3 | 4 | Compare different mysql drivers working with SQLAlchemy and gevent, see if they support cooperative multitasking using coroutines. 5 | 6 | The main purpose of this test is to find which mysql driver has best concurrency performance when we use SQLAlchemy and gevent together. 7 | So we won't test umysql, which isn't compatible with DBAPI 2.0. 8 | 9 | ## Verdict 10 | 11 | - Pure c version mysql driver has best performance in low concurrency scenario. It's about 2x to 2.5x faster than pure python version. Oursql is the fatest in this category. 12 | - Pure python mysql driver support gevent patch without any hack. And they brings same level of concurrency performance with gevent. PyMySQL has a consistent codebase and good community polularity at pure python mysql driver category. mysql-connector-python has better score, but the pypi package has some problem, so we will won't use it for now. PyMySQL is our choice this time. 13 | - greenify patched MySQLdb (the official native c mysql driver) gives best overall concurrency and absolute performance, but it has a complicated compile process, and we need to rely on a fork of official MySQLdb codebase which is not stable over time. 14 | 15 | ## Result 16 | 17 | ### 100 sessions, 20 concurrency, each session take 0.5 seconds (by `select sleep`), simulate high concurrency scenario 18 | 19 | ``` 20 | mysql://root:@localhost:3306/mysql_drivers_test total 100 (20) 50.5239 seconds 21 | mysql+pymysql://root:@localhost:3306/mysql_drivers_test total 100 (20) 2.6847 seconds 22 | mysql+oursql://root:@localhost:3306/mysql_drivers_test total 100 (20) 50.4289 seconds 23 | mysql+mysqlconnector://root:@localhost:3306/mysql_drivers_test total 100 (20) 2.6682 seconds 24 | ``` 25 | 26 | With greenify ptached MySQLdb (mysql-python). 27 | 28 | ``` 29 | mysql://root:@localhost:3306/mysql_drivers_test total 100 (20) 2.5790 seconds 30 | mysql+pymysql://root:@localhost:3306/mysql_drivers_test total 100 (20) 2.6618 seconds 31 | mysql+oursql://root:@localhost:3306/mysql_drivers_test total 100 (20) 50.4437 seconds 32 | mysql+mysqlconnector://root:@localhost:3306/mysql_drivers_test total 100 (20) 2.6340 seconds 33 | ``` 34 | 35 | Pure python driver support gevent's monkey patch, so they support cooperative multitasking using coroutines. 36 | That means the main thread won't be block by MySQL calls when you use PyMySQL or mysql-connector-python. 37 | 38 | ### 1000 sessions, 100 concurrency, each session only have 1 insert and 1 select, simulate high throughput low concurrency scenario 39 | 40 | ``` 41 | mysql://root:@localhost:3306/mysql_drivers_test total 1000 (100) 10.1098 seconds 42 | mysql+pymysql://root:@localhost:3306/mysql_drivers_test total 1000 (100) 26.8285 seconds 43 | mysql+oursql://root:@localhost:3306/mysql_drivers_test total 1000 (100) 6.4626 seconds 44 | mysql+mysqlconnector://root:@localhost:3306/mysql_drivers_test total 1000 (100) 22.4569 seconds 45 | ``` 46 | 47 | Oursql is faster than MySQLdb in this case. 48 | In pure python driver, mysql-connector-python is a bit faster than PyMySQL. 49 | use greenify or not won't affect the testing result in this user scenario. 50 | 51 | ## How to setup greenify 52 | 53 | ``` 54 | mkvirtualenv mysql_drivers_test # workon mysql_drivers_test 55 | pip install --upgrade setuptools pip cython 56 | pip install -r requirements.txt 57 | python mysql_drivers_comparison.py 58 | ``` 59 | 60 | Test with greenify MySQL-python (on OSX with homebrew). 61 | 62 | ``` 63 | mkvirtualenv mysql_drivers_test # workon mysql_drivers_test 64 | pip install --upgrade setuptools pip cython 65 | pip install -r requirements.txt 66 | git clone https://github.com/CMGS/greenify 67 | cd greenify 68 | cmake -G 'Unix Makefiles' -D CMAKE_INSTALL_PREFIX=$VIRTUAL_ENV CMakeLists.txt 69 | make & make install 70 | cd .. 71 | export LIBGREENIFY_PREFIX=$VIRTUAL_ENV 72 | pip install git+git://github.com/CMGS/greenify.git#egg=greenify # if you are using zsh, use \#egg=greenify 73 | git clone https://github.com/CMGS/mysql-connector-c 74 | cd mysql-connector-c 75 | export DYLD_LIBRARY_PATH=$VIRTUAL_ENV/lib 76 | cmake -G 'Unix Makefiles' -D GREENIFY_INCLUDE_DIR=$VIRTUAL_ENV/include -D GREENIFY_LIB_DIR=$VIRTUAL_ENV/lib -D WITH_GREENIFY=1 -D CMAKE_INSTALL_PREFIX=$VIRTUAL_ENV CMakeLists.txt 77 | make & make install 78 | cd .. 79 | git clone https://github.com/CMGS/MySQL-python.git 80 | cd MySQL-python 81 | export DYLD_LIBRARY_PATH=$VIRTUAL_ENV/lib 82 | export LIBRARY_DIRS=$VIRTUAL_ENV/lib 83 | export INCLUDE_DIRS=$VIRTUAL_ENV/include 84 | unlink /usr/local/lib/libmysqlclient.18.dylib 85 | ln -s $VIRTUAL_ENV/lib/libmysql.16.dylib /usr/local/lib/libmysqlclient.18.dylib 86 | python setup.py install 87 | brew switch mysql [version] # brew switch mysql 5.6.15 on my env, brew info mysql to check which version is available on your env 88 | cd .. 89 | python mysql_drivers_comparison.py 90 | ``` 91 | 92 | If the greenify doesn't work for you, you can use `otool -L _mysql.so` in your `$VIRTUAL_ENV/lib/python2.7/site-packages` MySQL-python folder. Can't find otool even after you installed XCode's command line tools? Follow this [link](http://reversi.ng/post/63714801645/make-otx-works-in-os-x-mavericks-with-xcode-5). 93 | 94 | I need say thank you to [CMGS](https://github.com/CMGS/). He guided me how to install greenify and how it works, he also help me triage the issues I met (include the `otool` part). Make greenify and mysql work on OSX make no sense, you shold do it on your application server which probably will be a linux, hope you will figure out how to. 95 | 96 | --------------------------------------------------------------------------------