📚

MySQLとSQLAlchemyで詰まった話

2023/09/22に公開

MySQLとPythonを利用して自社サービスを開発しています。開発していく中で解決に時間がかかったので原因と解決方法をまとめました。

環境

MYSQL: 8.0
Python: 3.9
SQLAlchemy: 1.4.36

開発環境の構築にはDockerを利用しています。

エラーになったコード

要件上、idとsystem_timestampというtimestamp型を複合主キーにする必要がありました。

models.py
from sqlalchemy import create_engine
from sqlalchemy.orm import declarative_base, sessionmaker
from datetime import datetime
from sqlalchemy import (Column, TIMESTAMP, Integer)

Engine = create_engine(
    f"mysql+pymysql://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:{DB_PORT}/{DB_NAME}?charset=utf8",
    encoding="utf-8"
)
BaseModel = declarative_base()
sync_session = sessionmaker(
    autocommit=False, autoflush=False, bind=Engine
)
db = sync_session()

class TestTable(BaseModel):
"""
idとsystem_timestampという複合主キーを持つ。
"""
    id = Column('id', Integer, primary_key=True, autoincrement=True)
    system_timestamp = Column(TIMESTAMP, primary_key=True, default=datetime.now)

test.py
from sqlalchemy.orm import Session
from models import db, TestTable

def create(db: Session):
    test = TestTable()
    db.add(test)
    db.commit()
    # ここでエラー発生
    print(test.id)
       ##############

エラーメッセージ

SQLAlchemyから、ObjectDeletedErrorが出ていました。
ググったところ、DBのレコードを直接削除した時にメモリに残っている値を参照すると出るエラーのようでした。

sqlalchemy.orm.exc.ObjectDeletedError: Instance '<TestTable at 0xffff9d99999>' has been deleted, or its row is otherwise not present.

しかし、レコードを削除などはしていませんし、DBにレコードは追加されています。

原因

system_timestampカラムにデフォルトでdatetime.nowで保存していた時間が、メモリ(SQLAlchemy側)では小数点6桁まで生成されているのに対し、mysql側では小数点以下切り捨てなのが原因のようです。

TODO: 詳細書く

解決策

解決策として、mysqlのカラムが小数点6桁を保存できるようにしました。

- from sqlalchemy import (TIMESTAMP)
+ from sqlalchemy.dialects.mysql import TIMESTAMP as MYSQL_TIMESTAMP

~省略~

-   system_timestamp = Column(TIMESTAMP, primary_key=True, default=datetime.now)
+   system_timestamp = Column(MYSQL_TIMESTAMP(fsp=6), primary_key=True, default=datetime.now)

もしくは、必要なければ以下のようにpython側で小数点以下まで生成しないことで解決できます。

datetime.now().replace(microsecond=0)

参考

Discussion