updateとinsertを順次行う処理で、updateの成功後にinsertが失敗したらupdateを取り消すような処理を書いたところ、insertが失敗してもupdateの内容が反映されてしまい困ったのでその際のメモ書きです。
結論
次のように書くと想定通りにトランザクションが機能するようになりました。
下記の例ではエラーハンドリングを省略して記載しています。
// NewMySQL is create connection to MySQL using sqlx =dsn (Data Source Name)
//db は *sqlx.DB 型
db, err = sqlx.Open("mysql", dsn)
// トランザクション開始 tx は *sqlx.Tx 型
tx, err := db.Beginx()
// UPDATE
st, err := tx.PrepareNamed("UPDATE aaa SET bbb=ccc WHERE id=:id")
res, err := st.Exec(map[string]interface{}{"id": ddd,}) // SQL実行
if err != nil { err = tx.Rollback() } // SQLの実行が失敗したらロールバックする
affected, err := res.RowsAffected() // 更新された行数を返す
// INSERT
st, err := tx.PrepareNamed("INSERT INTO aaa(bbb, ccc) VALUES (:id, :name)")
res, err := st.Exec(map[string]interface{}{"id": ddd, "name": eee}) // SQL実行
if err != nil { err = tx.Rollback() } // SQLの実行が失敗したらロールバックする
affected, err := res.RowsAffected() // 挿入された行数を返す
// コミット
err = tx.Commit()
上手くいかなかった原因
tx.PrepareNamed
と書くべきところをdb.PrepareNamed
と書いてしまったのが原因でした。
db は *sqlx.DB
型で、tx は *sqlx.Tx
型です。
トランザクションを張らない場合は、dbを渡しても例のコードは実行できるのでなかなか書き間違いに気づくことができませんでした。
// NewMySQL is create connection to MySQL using sqlx =DSN (Data Source Name)
//db は *sqlx.DB 型
db, err = sqlx.Open("mysql", dsn)
// トランザクション開始 tx は *sqlx.Tx 型
tx, err := db.Beginx()
// UPDATE
// 下記がdbになっている
st, err := db.PrepareNamed("UPDATE aaa SET bbb=ccc WHERE id=:id")
res, err := st.Exec(map[string]interface{}{"id": ddd,}) // SQL実行
if err != nil { err = tx.Rollback() } // SQLの実行が失敗したらロールバックする
affected, err := res.RowsAffected() // 更新された行数を返す
// INSERT
// 下記がdbになっている
st, err := db.PrepareNamed("INSERT INTO aaa(bbb, ccc) VALUES (:id, :name)")
res, err := st.Exec(map[string]interface{}{"id": ddd, "name": eee}) // SQL実行
if err != nil { err = tx.Rollback() } // SQLの実行が失敗したらロールバックする
affected, err := res.RowsAffected() // 挿入された行数を返す
// コミット
err = tx.Commit()
このままリリースしたら惨事がおきますね;