🦔
【SQLAlchemy】SELECTしかしていなくてもcommitを実行させた方がいい場合
はじめに
python
でDB操作するときは、SQLAlchemy
を利用しています。
その中で、selectしても最新のレコードが取得されないって事で嵌ったので備考録として残します。
起こったこと
下記検証コードのように、DBからレコードを取得し処理対象があればメイン処理をするというバッチ処理を想定する。
他のバッチでflg
を更新してコミットされても、★1で取得する値が変わらずif
の中にいつまたっても入ってくれなかった。
import datetime
import threading
import time
from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session, sessionmaker
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.schema import Column
from sqlalchemy.types import Integer, Date, DateTime, String, Numeric
# DB接続セッション
db_host = "localhost"
db_user = "hoge"
db_pass = "passw0rd"
db_name = "hogedb"
db_engine = create_engine(f'mysql://{db_user}:{db_pass}@{db_host}/{db_name}?charset=utf8')
db_session = scoped_session(
sessionmaker(
autocommit=False,
autoflush=True,
bind=db_engine,
)
)
DbBase = declarative_base()
# テーブル定義
class Sample(DbBase):
__tablename__ = "sample"
id = Column(Integer, primary_key=True)
flg = Column(Integer)
def get_flg():
return db_session.query(Sample).filter_by(id=1).first().flg
while True:
execute_flg = get_flg() # ★1
if execute_flg:
# 何らかのDB処理(commit or rollback でトランザクション意識して作成)
print("実行しました")
print(str(datetime.datetime.now()) + ":現在のフラグ=" + str(execute_flg))
time.sleep(1)
結論
-
SQLAlchemy
のautocommit
をFalse
- 無限ループで常駐
-
MySQL
のデフォルト分離レベルは、REPEATABLE READ
-
MySQL
のトランザクションはcommit
orrollback
の直後に貼りなおされる他、最初のselect
時にもトランザクションが開始される模様
これらの組み合わせで発生してた模様。
ざっくり説明
- autocommitが
False
のため、明示的にcommit
orrollback
をしない限りプログラム終了までトランザクションは終了しない。 - 最初のフラグ取得の
select
で自動でトランザクションが開始されている。 - 分離レベルが、
REPEATABLE READ
、トランザクション開始後に他バッチで更新された値は読み取れない - だから、
if
の中は実行されない。
対処
select
だけしかしていないが、ループの最後に明示的にcommit
を追加してあげればいい。
while True:
execute_flg = get_flg()
if execute_flg:
# 何らかのDB処理(commit or rollback でトランザクション意識して作成)
print("実行しました")
print(str(datetime.datetime.now()) + ":現在のフラグ=" + str(execute_flg))
db_session.commit() # commitを明示的に実行
time.sleep(1)
さいごに
明示的にトランザクションを切る場合はいいが、select
だけだしトランザクションあまり考えなくてもいいや・・・って思ってたら、こういうところではまる…
Discussion