MySQLでのトランザクション処理をGolang+dbrで実現してゆく話

この記事は最終更新日から3年以上が経過しています。

環境

Go :go version go1.6.2 darwin/amd64
IDE :VSCode1.1.0
echo:Echo v2.(beta)
MySQL: Ver 14.14 Distrib 5.7.12, for osx10.11 (x86_64)
OS:Max OSX El Capitan
俺 :Go初めて3日目
MySQLはHomebrewで入れました。

トランザクションとは

トランザクションとは、連続する複数のデータ操作のまとまりのこと

特徴として:
- ユーザはトランザクション単位(複数のデータ操作)での取り消しと確定ができます(トランザクション処理)
- 複数のデータ操作が連なっている時、「複数の更新処理を連続して行う際に、すべての処理が成功したときにのみデータベースへの変更を有効としなければならないデータ操作」では、このトランザクション処理は必須です。
- ただのロックとは違います。

例としては
①銀行口座に1000円を振り込む(=財布の中身から1000円減る)
②口座の預金が1000円増える
というのがあり、SQL的にかけば

①Update 財布 SET 中身 - 1000 where 持ち主=自分
②Update 口座 SET 預金 + 1000 where 持ち主=自分

となるわけですが、この場合①②がセットでトランザクションです。

そして例えばトランザクション処理が無かった場合:
①が成功したのに、②が失敗したとしましょう。すると自分の財布から1000円消えただけになってしまいます。

こういったことを防いで、①と②がどちらも成功したらよしとする。途中でエラーが出て完遂できなかったらこれら一連の処理を無かったことにする
これがトランザクション処理です。

トランザクション処理

複数のデータ操作処理を連続して行う際に、すべての処理が成功したときにのみデータベースへの変更を有効とする、複数のデータ操作のセット

トランザクション処理の開始はデータベースが自動的に解釈することはないため自分で開始の宣言をする必要がありますね。

決めること:
・どこからがトランザクション処理かを宣言する(Begin)
・ここまで成功したら良しとして処理する宣言をする(Commit)
・何か間違い(エラー)が発生したらBegin以前まで戻す(Rollback)

先ほどの例でトランザクション処理を行うなら:

トランザクション処理を開始するBegin()
①Update 財布 SET 中身 - 1000 where 持ち主=自分
  if (①でエラー){Rollback()}

②Update 口座 SET 預金 + 1000 where 持ち主=自分
  if (②でエラー){Rollback()}

③①と②が正しく処理できたのでよしとするCommit()

②が失敗した時点で①を行う前の状態に戻ります。

つまり、①が成功したのに、②が失敗したとしてもRollbackが行われます。
それによって①を行う前、つまり”財布から1000円を取り出していない状態”に戻ります。
トランザクションさまさまですね。

Golang+dbrでそれを実現する

前提

dbrとMySQLのCRUDについては前回の僕の記事を見てください。
http://qiita.com/CST_negi/items/5e276ddc0412cefef7e3

コード

server.go
func transactionTest(c echo.Context) error {
    //トランザクション開始
    tx,_ := sess.Begin()

    //①財布から1000円取り出して振り込む。
    balance_at_wallet -= 1000 //DBから残高を取得する処理は省略(本来はSELECT FOR UPDATEでとってくるべきもの) 
    attrsMap := map[string]interface{}{"balance": balance_at_wallet}
    _,err:=sess.Update("wallet").SetMap(attrsMap).Where("user = ?", 'me').Exec()    

        if err != nil {
            //エラーが発生したのでこれは失敗
            println("エラー:財布から1000円を振り込むのに失敗しました")
            //ロールバックして開始前に戻る
            tx.Rollback()   
            //ロールバックが完了したので、エラー終了する。
            return c.NoContent(http.StatusInternalServerError)
        } else {
            //エラーがなかったので①の処理は成功
            println("正常:財布から1000円を振り込みました。")

        }

    //②口座の預金を1000円増やす
    balance_at_bank += 1000 //DBから残高を取得する処理は省略(本来はSELECT FOR UPDATEでとってくるべきもの) 
    attrsMap := map[string]interface{}{"balance": balance_at_bank}
    _,err:=sess.Update("bank").SetMap(attrsMap).Where("user = ?", 'me').Exec()  

        if err != nil {
            //エラーが発生したのでこれは失敗
            println("エラー:口座の預金残高を増やすのに失敗しました。")
            //ロールバックして開始前に戻る
            tx.Rollback()   
            //ロールバックが完了したので、エラー終了する。
            return c.NoContent(http.StatusInternalServerError)
        } else {
            //エラーがなかったので②の処理は成功
            println("正常:口座の預金が1000円増えました。")
        }


    }
    //①、②ともに正常に進んだのでトランザクション処理を確定して、両方の処理を有効にする。
    tx.Commit()
    //正常終了する
    return c.NoContent(http.StatusOK)
}

こんな感じでしょうか。
仮に①の処理が成功したとして、②が失敗したとしてもログやDBの状況を確認すると①の処理が行われる以前に戻っていることがわかると思います。

トランザクション処理はDB操作においてほぼ必須となる処理になるので抑えておきたいものです。
・これはセットでやらないとおかしくなるなという処理をまとめる
・Begin - DB操作 if(error){Rollback} - Commitというコードを書く。
というだけなので、処理のまとまりさえ理解していればコードを書く方は大丈夫だと思います。

以上です。

ユーザー登録して、Qiitaをもっと便利に使ってみませんか。
  1. あなたにマッチした記事をお届けします
    ユーザーやタグをフォローすることで、あなたが興味を持つ技術分野の情報をまとめてキャッチアップできます
  2. 便利な情報をあとで効率的に読み返せます
    気に入った記事を「ストック」することで、あとからすぐに検索できます
コメント
この記事にコメントはありません。
あなたもコメントしてみませんか :)
すでにアカウントを持っている方は
ユーザーは見つかりませんでした