Golangでトランザクションを使う話

今までに作ったWebサービスは基本的にハッカソンで作ったものだけで、いつも適当に動けばいいやと思ってトランザクションを今まで使ったことなかったんですけど、この前初めてトランザクションを書いたのでその話。

Golangトランザクションを使う

今回は標準のdatabase/sqlパッケージを使っています。

大雑把に言うと(*sql.DB)Begin()でトランザクションを開始して、(*sql.Tx)Commit()でコミット、(*sql.Tx)RollBack()でロールバックします。

gistfcc39f479d2577fe2467ee379026325a

Begin()でトランザクションを開始してからは、sql.Txのメソッドを使って操作を行います。

この例だとOpen()とかBegin()とかでerrを握りつぶしてるので、実際に使うときはエラー処理を書いてください。 

 

トランザクション内でのクエリ発行

基本的にsql.DBと同じ感じでsql.Txのメソッドを使ってクエリを投げられます。

gistf83543832a3df58934b1c45fff32da52

Exec()もQuery()もQueryRow()も同じように使えます。戻り値もsql.DBと同様です。

 

prepared statementを使う

さっき「『基本的に』sql.DBと同じ感じ」と言ったのは、prepared statementを使うときに若干違いがあるからです。

(*sql.Tx)Prepare()でsql.Stmtを作った場合は、特に意識する必要はないんですが、問題は(*sql.DB)Prepare()で作ったsql.Stmtをトランザクション内で使うときです。

トランザクション外で作ったprepared statementを使う場合は(*sql.Tx)Stmt()を使って変換する必要があります。

gista4761152f440845fa3f3544a347915a8

(*sql.DB).Prepare()で作ったprepared statementは(*sql.DB)Begin()を呼んだあとでも、トランザクションには含まれません。

gist74ddcda1ca545cfd1fa3662668c7a7bb

(*sql.Tx).Prepare()で作ったprepared statementはそのまま使うことができます。 

 

ランタイムパニックで落ちてもロールバックさせる

スターティングGo言語に載っているdeferとrecover()を使うイディオムで、何らかの理由でランタイムパニックが発生してアプリケーションが落ちたときにもきちんとロールバックさせることができます。

gistee409b7c9faca0c8e6bfbd0e0dc9a353

呼んだ先でpanicが起きる前提で例外処理のようなことをするのはGo言語的ではないですけど、意図せず配列外参照とかをしちゃったときはpanicが起きるので、きちんとこういうことを書いておくにこしたことはないです。

 

まとめ

とりあえず要するにこう。

gist285745829260ddfaba34e472ab478ba5

記事の途中でも言ってますが、ここで挙げているサンプルはerrを握りつぶしているので、そのままコピペして使うと怒られます。

ブログ書くとき、いちいちif err != nil{}を書くとそれだけで3行取られて長くなってしまうので、あんまり書きたくないんですけど、うまい落とし所はないんですかね。

BeginTx()でコンテキスト付きのトランザクションを張れるみたいなので、サービス全体でコンテキストを引回すようなときはこっちの方がいいと思います。