SQLAlchemy+MySQLでUpdate文に失敗

ハマった問題の原因調査結果です。

現象

sqlalchemy.orm.exc.ConcurrentModificationError: Updated rowcount 0 does not match number of objects updated 1

原因不明なこんなメッセージに悩まされてました。

再現条件

  • SQLAlchemy
  • MySQL
  • アダプタがMySQLdb

再現スクリプト

動作条件

Ubuntu 10.10で必要なパッケージは

以下のデータベースを作成済みであること

CREATE DATABASE practice DEFAULT CHARACTER SET utf8 COLLATE utf8_bin
スクリプト

コメントにしてますが、比較用にSQLiteでの動作なども確認できます。

#! -*- coding: utf-8 -*-
from sqlalchemy import Column, String, Numeric, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

Base = declarative_base()
class Sample(Base): # Sampleクラスとsamplesテーブルとそのマッピングの定義
    __tablename__ = 'samples' # テーブル名
    __table_args__ = {'mysql_engine':'InnoDB'} # MySQLではInnoDB

    id = Column(Numeric(18,0), primary_key=True) # IDカラムの定義
    val = Column(String(10))                  # VALカラムの定義

    def __init__(self, id, val):
        self.id = id
        self.val = val

# SQLiteとMySQLのデータベースの定義
# MySQLは事前にCREATE DATABASE practice DEFAULT CHARACTER SET utf8 COLLATE utf8_bin(大文字小文字区別あり)
engines = (
#    create_engine("sqlite:////home/user1/practice.sqlite", echo=True),
    create_engine("mysql+mysqldb://root@localhost/practice?charset=utf8&use_unicode=0", echo=True),
)

# セッションクラスの作成とセッションオブジェクトの作成(SQLiteとMySQL両方)
Sessions = []
sessions = []
for engine in engines:
    # create table
    Base.metadata.create_all(engine)
    # セッション生成
    Sessions.append(sessionmaker(bind=engine))
    sessions.append(Sessions[-1]())

# SQLの実行
for session in sessions:
    # insert into samples values(xxx, 'val1')
    session.add(Sample(19778376323571713, "val2"))
    # commit
    session.commit()
    
    # select * from samples
    for sample in session.query(Sample):
        print sample.id, sample.val
        # update samples set val='削除します' where id=?
        sample.val = u'削除します'
        # commit
        session.commit()
        # delete from samples where id=?
        session.delete(sample)
        # commit
        session.commit()
    
    session.close()

原因

update時に

UPDATE samples SET val='削除します' WHERE samples.id = '19778376323571713'

なクエリが出ていたこと。MySQLのマニュアル*1によると、この文字列は一旦53bitの浮動小数点数に丸められるため、この桁数は表現できず、WHERE句の条件では一致しない。

正しくは

UPDATE samples SET val='削除します' WHERE samples.id = 19778376323571713

でないといけない。

回避策

この例ではNumericの代わりにBigIntegerを使用する


とても助かったページ↓(マニュアルの翻訳)
http://omake.accense.com/static/doc-ja/sqlalchemy/index.html