Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 14636] ファイルの概要

このコミットは、Go言語の標準ライブラリである database/sql パッケージにおけるエラーハンドリングの改善に関するものです。具体的には、データベースドライバが driver.ErrBadConn を返した場合に、*DB.begin メソッドがそのエラーをそのまま返すように変更することで、database/sql パッケージが接続不良を適切に検知し、より堅牢な接続管理を行えるようにしています。

コミット

commit 309eae19235349d39053f06887f0384c5757fa3e
Author: James David Chalfant <james.chalfant@gmail.com>
Date:   Wed Dec 12 22:04:55 2012 -0800

        database/sql: Alter *DB.begin to return driver.ErrBadConn when driver.Conn.Begin returns driver.ErrBadConn
    Fixes #4433
    
    R=golang-dev, bradfitz
    CC=golang-dev
    https://golang.org/cl/6845094

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/309eae19235349d39053f06887f0384c5757fa3e

元コミット内容

database/sql: Alter *DB.begin to return driver.ErrBadConn when driver.Conn.Begin returns driver.ErrBadConndatabase/sql: *DB.begindriver.Conn.Begindriver.ErrBadConn を返した場合に driver.ErrBadConn を返すように変更)

このコミットは、database/sql パッケージ内の *DB.begin メソッドの動作を変更し、基盤となるデータベースドライバの driver.Conn.Begin メソッドが driver.ErrBadConn エラーを返した場合に、そのエラーをラップせずに直接返すように修正しています。これにより、database/sql パッケージが接続の不良状態を正確に認識し、適切な処理(例えば、その接続をプールから削除して新しい接続を確立するなど)を実行できるようになります。

変更の背景

Goの database/sql パッケージは、データベースとのやり取りを抽象化し、様々なデータベースドライバを統一的に扱うためのインターフェースを提供します。このパッケージの重要な機能の一つに、データベース接続のプールと管理があります。データベース接続は、ネットワークの問題、データベースサーバーの再起動、アイドルタイムアウトなど、様々な理由で無効になることがあります。

driver.ErrBadConn は、Goの database/sql/driver パッケージで定義されている特別なエラーで、データベースドライバが「この接続はもう使えない」と database/sql パッケージに伝えるためのシグナルとして機能します。database/sql パッケージは、この driver.ErrBadConn を受け取ると、その接続を無効と判断し、接続プールから削除して再利用しないようにします。これにより、アプリケーションが壊れた接続を使い続けようとすることを防ぎ、より堅牢なデータベース操作を実現します。

このコミット以前は、*DB.begin メソッド内で driver.Conn.Begindriver.ErrBadConn を返した場合でも、そのエラーは fmt.Errorf("sql: failed to Begin transaction: %v", err) のように新しいエラーメッセージでラップされていました。エラーがラップされると、database/sql パッケージは errors.Is (または Go 1.13 以前の err == driver.ErrBadConn) を使って元の driver.ErrBadConn を直接識別することができませんでした。結果として、database/sql パッケージは接続が不良であることを認識できず、壊れた接続を接続プールに保持し続け、後続の操作で再び失敗する可能性がありました。

この問題は、GoのIssue #4433として報告されており、このコミットはその修正を目的としています。

前提知識の解説

  • database/sql パッケージ: Go言語の標準ライブラリの一部で、SQLデータベースとの対話のための汎用的なインターフェースを提供します。特定のデータベースシステムに依存しないコードを書くことを可能にし、データベースドライバを介して実際のデータベース操作を行います。
  • database/sql/driver パッケージ: database/sql パッケージがデータベースドライバと通信するためのインターフェースを定義しています。各データベース(PostgreSQL, MySQL, SQLiteなど)に対応するドライバは、このインターフェースを実装する必要があります。
  • driver.Conn インターフェース: database/sql/driver パッケージ内で定義されており、単一のデータベース接続を表します。このインターフェースには、トランザクションを開始するための Begin() メソッドなどが含まれます。
  • driver.ErrBadConn: database/sql/driver パッケージで定義されている特別なエラー変数です。ドライバがこのエラーを返した場合、database/sql パッケージは、その接続が使用不可能であり、接続プールから削除して再利用すべきではないと解釈します。これは、データベース接続が切断されたり、無効になったりした場合にドライバが database/sql パッケージに通知するためのメカニズムです。
  • エラーラッピングとアンラッピング: Go 1.13以降では、fmt.Errorf%w 動詞を使用することでエラーをラップし、errors.Iserrors.As を使って元のエラー(ラップされたエラー)を検査できるようになりました。しかし、このコミットが作成された2012年時点では、このようなエラーラッピングのメカニズムは存在せず、エラーをラップすると元のエラーの型を失うのが一般的でした。そのため、driver.ErrBadConn のような特定の型のエラーを識別するためには、エラーがラップされていない状態で直接比較する必要がありました。

技術的詳細

このコミットの核心は、database/sql パッケージが driver.ErrBadConn を正しく認識できるようにすることです。database/sql パッケージは、データベース操作中にエラーが発生した場合、そのエラーが driver.ErrBadConn であるかどうかをチェックします。もしそうであれば、その接続は不良であると判断し、接続プールから削除して、次の操作のために新しい接続を確立しようとします。

*DB.begin メソッドは、新しいトランザクションを開始するためにデータベース接続を取得し、その接続の Begin() メソッドを呼び出します。もし Begin() メソッドがエラーを返した場合、このコミット以前は以下のようなコードでした。

if err != nil {
    db.putConn(ci, err)
    return nil, fmt.Errorf("sql: failed to Begin transaction: %v", err)
}

ここで fmt.Errorf を使用してエラーをラップしているため、database/sql パッケージのより上位の層では、返されたエラーが driver.ErrBadConn であるかどうかを直接判断できませんでした。

このコミットでは、この行を以下のように変更しています。

if err != nil {
    db.putConn(ci, err)
    return nil, err
}

この変更により、driver.Conn.Begin() から返されたエラーが driver.ErrBadConn であった場合、そのエラーがそのまま *DB.begin の呼び出し元に伝播されます。これにより、database/sql パッケージの接続管理ロジックが driver.ErrBadConn を検出し、不良な接続を適切に処理できるようになります。具体的には、db.putConn(ci, err) が呼び出された際に、errdriver.ErrBadConn であれば、putConn 内部でその接続を不良としてマークし、接続プールに戻さない、あるいはプールから削除するなどの処理が行われます。

この修正は、database/sql パッケージの堅牢性を高め、データベース接続が予期せず切断された場合でも、アプリケーションが自動的に回復し、新しい有効な接続を使用して操作を続行できるようにするために不可欠です。

コアとなるコードの変更箇所

変更は src/pkg/database/sql/sql.go ファイルの func (db *DB) begin() (tx *Tx, err error) 関数内で行われています。

--- a/src/pkg/database/sql/sql.go
+++ b/src/pkg/database/sql/sql.go
@@ -426,7 +426,7 @@ func (db *DB) begin() (tx *Tx, err error) {
 	txi, err := ci.Begin()
 	if err != nil {
 		db.putConn(ci, err)
-		return nil, fmt.Errorf("sql: failed to Begin transaction: %v", err)
+		return nil, err
 	}
 	return &Tx{
 		db:  db,

コアとなるコードの解説

変更された行は以下の通りです。

変更前:

return nil, fmt.Errorf("sql: failed to Begin transaction: %v", err)

この行では、ci.Begin() から返されたエラー err を、"sql: failed to Begin transaction: %v" という新しいフォーマット文字列でラップしていました。これにより、元のエラー err の型情報(特に driver.ErrBadConn であるかどうか)が失われ、呼び出し元で driver.ErrBadConn を直接識別することが困難になっていました。

変更後:

return nil, err

この行では、ci.Begin() から返されたエラー err をそのまま返しています。これにより、もし errdriver.ErrBadConn であった場合、その情報がそのまま呼び出し元に伝達されます。database/sql パッケージの接続管理ロジックは、この driver.ErrBadConn を受け取ることで、接続が不良であることを認識し、その接続を接続プールから削除するなどの適切な回復処理を実行できるようになります。

このシンプルな変更は、database/sql パッケージがデータベース接続の健全性をより正確に判断し、アプリケーションの信頼性と回復力を向上させる上で非常に重要な役割を果たします。

関連リンク

参考にした情報源リンク