SQLAlchemy との戦い
ここ数ヶ月 SQLAlchemy を使って開発をしている。
開発当初は特に問題もなく調子よく行っていたが、自分のローカル環境の MySQL の設定を本番に近づけたため、SQLAlchemy がエラーをはいた - Memo が出た。
pool_recycle の値を短くすれば解決と思ったが、解決しなかった。
前提として FW は Flask を利用。SQLAlchemy は素で scoped_session を使っている。
Flask-SQLAlchey や Flask-Alchemy-Session は使ってない。
autocommit, autoflush は False の設定。
でたエラーが
sqlalchemy.exc.OperationalError: (pymysql.err.OperationalError) (2013, 'Lost connection to MySQL server during query')
だったり
sqlalchemy.exc.StatementError: (sqlalchemy.exc.InvalidRequestError) Can't reconnect until invalid transaction is rolled back
だったり。
現象としては、一つのページにとどまっていて、wait_timeout で設定した 60 秒を超えて、ページをリロード(MySQL にアクセス)した際にでる。
# 更新系は出ない
とりあえずぐぐって、出てきたページを片っ端から試してみた。
SQLAlchemyの OperationalError: MySQL Connection not available エラー - Life is Really Short, Have Your Life!!
コメント欄にあるように、session.remove() をリクエストの最後に呼んでやる。
これは元々 Flask の teardown_request で呼んでいる。
効果無し。
FlaskとSQLAlchemyを使っててMySQL server has gone awayってなる - petitviolet_blog
SQLAlchemy+MySQLで「Lost connection to MySQL server during query」|python|サムライファクトリー開発ブログ
にあるように、Pool event を使って、MySQL リクエストごとに ping を飛ばす。
Connection Pooling — SQLAlchemy 1.3 Documentation
効果無し。
http://perezvon.hatenablog.com/entry/20071215/1197702294
@soundkitchen さんに教えて貰った。
リクエストの最後は漏れる可能性があるから、リクエストの最初に remove() してセッションを作り直してみたら? というもの。
効果なし。
更新系ではでなくて、参照系で出るのはなにか違いがあるのかと思って、コードを見直したら、session.commit() しているかしていないかの違いがあった。
これかと思って、試してみたら、正解だった。
これで万事解決したかと思ったが、依然でる所があった。
因みにでたのが、WTForms で SQLAlchemy からセレクトボックスに DB の項目を出している所だった。
こんな感じで、セッションのクローズ漏れが出てくるのが怖い。
SQLAlchemy — The Pyramid Community Cookbook v0.2
Pyramid のドキュメントではリクエストコンテキストの最後に、必ず commit, close をしていた。
これにならって、以下のようにした。
@app.teardown_appcontext def session_clear(exception): if exception and Session.is_active: session.rollback() else: #session.commit() 最初こうしてたけど、意図して Rollback した際に不具合おきるからなにもしない pass Session.close()
あと、session 生成時に、session.expire_on_commit = False しておかないと、例外が起きた。
DetachedInstanceError: Instance <MyModel at 0x36bb190> is not bound to a Session; attribute refresh operation cannot proceed
なので、sessionmaker の際に expire_on_commit を付けるようにした。
とりあえずこれで安定して動くようになった。