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